diff --git a/.github/actions/setup-poetry/action.yaml b/.github/actions/setup-poetry/action.yaml index 1209c5fa..9b26c607 100644 --- a/.github/actions/setup-poetry/action.yaml +++ b/.github/actions/setup-poetry/action.yaml @@ -15,7 +15,7 @@ runs: run: | pip install --user pipx pipx ensurepath - pipx install poetry + pipx install poetry ${{ runner.os == 'macOS' && '--python "$Python_ROOT_DIR/bin/python"' || '' }} shell: bash - name: Get full Python version diff --git a/.github/workflows/build-python.yaml b/.github/workflows/build-python.yaml index 9774d2d6..3384bd52 100644 --- a/.github/workflows/build-python.yaml +++ b/.github/workflows/build-python.yaml @@ -28,7 +28,7 @@ jobs: - name: Install PuyaPy if: ${{ matrix.python == '3.12' }} - run: pipx install puya + run: pipx install puyapy - name: pytest shell: bash diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 48ecbdae..ec4e026e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: entry: poetry run ruff format language: system types: [python] - args: [] + args: [--no-cache] require_serial: true additional_dependencies: [] minimum_pre_commit_version: "0" @@ -17,7 +17,7 @@ repos: entry: poetry run ruff language: system "types": [python] - args: [--fix] + args: [--fix, --no-cache] require_serial: false additional_dependencies: [] minimum_pre_commit_version: "0" diff --git a/README.md b/README.md index 92496581..003e63bb 100644 --- a/README.md +++ b/README.md @@ -46,9 +46,8 @@ AlgoKit helps you develop Algorand solutions: - **Development**: AlgoKit provides SDKs, tools and libraries that help you quickly and effectively build high quality Algorand solutions: - **AlgoKit Utils** ([Python](https://github.com/algorandfoundation/algokit-utils-py#readme) | [TypeScript](https://github.com/algorandfoundation/algokit-utils-ts#readme)): A set of utility libraries so you can develop, test, build and deploy Algorand solutions quickly and easily - [algosdk](https://developer.algorand.org/docs/sdks/) ([Python](https://github.com/algorand/py-algorand-sdk#readme) | [TypeScript](https://github.com/algorand/js-algorand-sdk#readme)) - The core Algorand SDK providing Algorand protocol API calls, which AlgoKit Utils wraps, but still exposes for advanced scenarios - - [**Beaker**](https://beaker.algo.xyz/): A productive Python framework for building Smart Contracts on Algorand. - - [PyTEAL](https://pyteal.readthedocs.io/en/stable/): The Python language bindings for Algorand Smart Contracts, which Beaker wraps - - [TEAL](https://developer.algorand.org/docs/get-details/dapps/smart-contracts/): Transaction Execution Approval Language (TEAL) is the assembly-like language interpreted by the Algorand Virtual Machine (AVM) running within an Algorand node, which Beaker exports + - [**Algorand Python**](https://github.com/algorandfoundation/puya): A semantically and syntactically compatible, typed Python language that works with standard Python tooling and allows you to express smart contracts (apps) and smart signatures (logic signatures) for deployment on the Algorand Virtual Machine (AVM). + - [**TEALScript**](https://github.com/algorandfoundation/TEALScript): A subset of TypeScript that can be used to express smart contracts (apps) and smart signatures (logic signatures) for deployment on the Algorand Virtual Machine (AVM). - [**AlgoKit LocalNet**](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/localnet.md): A local isolated Algorand network so you can simulate real transactions and workloads on your computer ### Operate @@ -62,7 +61,7 @@ AlgoKit comes with out-of-the-box [Continuous Integration / Continuous Deploymen The set of capabilities supported by AlgoKit will evolve over time, but currently includes: - Quickly run, explore and interact with an isolated local Algorand network (LocalNet) -- Building, testing, deploying and calling [Algorand PyTEAL](https://github.com/algorand/pyteal) / [Beaker](https://beaker.algo.xyz/) smart contracts +- Building, testing, deploying and calling [Algorand Python](https://github.com/algorandfoundation/puya) / [TEALScript](https://github.com/algorandfoundation/TEALScript) smart contracts For a user guide and guidance on how to use AlgoKit, please refer to the [docs](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/algokit.md). @@ -83,7 +82,7 @@ This is an open source project managed by the Algorand Foundation. See the [cont ## Prerequisites -The key required dependency is Python 3.10+, but some of the installation options below will install that for you. +The key required dependency is Python 3.10+, but some of the installation options below will install that for you. We recommend using Python 3.12+, as the `algokit compile python` command requires this version. AlgoKit also has some runtime dependencies that also need to be available for particular commands. @@ -126,10 +125,12 @@ AlgoKit can be installed using OS specific package managers, or using the python > **App Installer python.exe** or **App Installer python3.exe**. 3. Install pipx: + ``` pip install --user pipx python -m pipx ensurepath ``` + 4. Restart the terminal to ensure pipx is available on the path 5. Install AlgoKit via pipx: `pipx install algokit` 6. Restart the terminal to ensure AlgoKit is available on the path diff --git a/docs/cli/index.md b/docs/cli/index.md index 288da442..928c526d 100644 --- a/docs/cli/index.md +++ b/docs/cli/index.md @@ -7,75 +7,65 @@ - [-v, --verbose](#-v---verbose) - [--color, --no-color](#--color---no-color) - [--skip-version-check](#--skip-version-check) - - [bootstrap](#bootstrap) + - [compile](#compile) - [Options](#options-1) - - [--force](#--force) - - [all](#all) - - [Options](#options-2) - - [--interactive, --non-interactive, --ci](#--interactive---non-interactive---ci) - - [env](#env) - - [Options](#options-3) - - [--interactive, --non-interactive, --ci](#--interactive---non-interactive---ci-1) - - [npm](#npm) - - [poetry](#poetry) + - [-v, --version ](#-v---version-) + - [py](#py) + - [Arguments](#arguments) + - [PUYAPY_ARGS](#puyapy_args) + - [python](#python) + - [Arguments](#arguments-1) + - [PUYAPY_ARGS](#puyapy_args-1) - [completions](#completions) - [install](#install) - - [Options](#options-4) + - [Options](#options-2) - [--shell ](#--shell-) - [uninstall](#uninstall) - - [Options](#options-5) + - [Options](#options-3) - [--shell ](#--shell--1) - [config](#config) - [version-prompt](#version-prompt) - - [Arguments](#arguments) + - [Arguments](#arguments-2) - [ENABLE](#enable) - - [deploy](#deploy) - - [Options](#options-6) - - [-C, --command ](#-c---command-) - - [--interactive, --non-interactive, --ci](#--interactive---non-interactive---ci-2) - - [-P, --path ](#-p---path-) - - [--deployer ](#--deployer-) - - [--dispenser ](#--dispenser-) - - [Arguments](#arguments-1) - - [ENVIRONMENT_NAME](#environment_name) - [dispenser](#dispenser) - [fund](#fund) - - [Options](#options-7) + - [Options](#options-4) - [-r, --receiver ](#-r---receiver-) - [-a, --amount ](#-a---amount-) - [--whole-units](#--whole-units) - [limit](#limit) - - [Options](#options-8) + - [Options](#options-5) - [--whole-units](#--whole-units-1) - [login](#login) - - [Options](#options-9) + - [Options](#options-6) - [--ci](#--ci) - [-o, --output ](#-o---output-) - [-f, --file ](#-f---file-) - [logout](#logout) - [refund](#refund) - - [Options](#options-10) + - [Options](#options-7) - [-t, --txID ](#-t---txid-) - [doctor](#doctor) - - [Options](#options-11) + - [Options](#options-8) - [-c, --copy-to-clipboard](#-c---copy-to-clipboard) - [explore](#explore) - - [Arguments](#arguments-2) + - [Arguments](#arguments-3) - [NETWORK](#network) - [generate](#generate) - [client](#client) - - [Options](#options-12) + - [Options](#options-9) - [-o, --output ](#-o---output--1) - [-l, --language ](#-l---language-) - - [Arguments](#arguments-3) + - [-v, --version ](#-v---version--1) + - [Arguments](#arguments-4) - [APP_SPEC_PATH_OR_DIR](#app_spec_path_or_dir) - [goal](#goal) - - [Options](#options-13) + - [Options](#options-10) - [--console](#--console) - - [Arguments](#arguments-4) + - [Arguments](#arguments-5) - [GOAL_ARGS](#goal_args) - [init](#init) - - [Options](#options-14) + - [Options](#options-11) - [-n, --name ](#-n---name-) - [-t, --template ](#-t---template-) - [--template-url ](#--template-url-) @@ -91,33 +81,64 @@ - [console](#console) - [explore](#explore-1) - [logs](#logs) - - [Options](#options-15) + - [Options](#options-12) - [--follow, -f](#--follow--f) - [--tail ](#--tail-) - [reset](#reset) - - [Options](#options-16) + - [Options](#options-13) - [--update, --no-update](#--update---no-update) - [start](#start) - - [Options](#options-17) + - [Options](#options-14) - [-n, --name ](#-n---name--1) - [status](#status) - [stop](#stop) + - [project](#project) + - [bootstrap](#bootstrap) + - [Options](#options-15) + - [--force](#--force) + - [Options](#options-16) + - [--interactive, --non-interactive, --ci](#--interactive---non-interactive---ci) + - [-p, --project-name ](#-p---project-name-) + - [-t, --type ](#-t---type-) + - [Options](#options-17) + - [--interactive, --non-interactive, --ci](#--interactive---non-interactive---ci-1) + - [deploy](#deploy) + - [Options](#options-18) + - [-C, --command ](#-c---command-) + - [--interactive, --non-interactive, --ci](#--interactive---non-interactive---ci-2) + - [-P, --path ](#-p---path-) + - [--deployer ](#--deployer-) + - [--dispenser ](#--dispenser-) + - [-p, --project-name ](#-p---project-name--1) + - [Arguments](#arguments-6) + - [ENVIRONMENT_NAME](#environment_name) + - [link](#link) + - [Options](#options-19) + - [-p, --project-name ](#-p---project-name--2) + - [-l, --language ](#-l---language--1) + - [-a, --all](#-a---all) + - [-f, --fail-fast](#-f---fail-fast) + - [-v, --version ](#-v---version--2) + - [list](#list) + - [Arguments](#arguments-7) + - [WORKSPACE_PATH](#workspace_path) + - [run](#run) - [task](#task) - [analyze](#analyze) - - [Options](#options-18) + - [Options](#options-20) - [-r, --recursive](#-r---recursive) - [--force](#--force-1) - [--diff](#--diff) - [-o, --output ](#-o---output--2) - [-e, --exclude ](#-e---exclude-) - - [Arguments](#arguments-5) + - [Arguments](#arguments-8) - [INPUT_PATHS](#input_paths) - [ipfs](#ipfs) - - [Options](#options-19) + - [Options](#options-21) - [-f, --file ](#-f---file--1) - [-n, --name ](#-n---name--2) - [mint](#mint) - - [Options](#options-20) + - [Options](#options-22) - [--creator ](#--creator-) - [-n, --name ](#-n---name--3) - [-u, --unit ](#-u---unit-) @@ -129,37 +150,37 @@ - [--nft, --ft](#--nft---ft) - [-n, --network ](#-n---network-) - [nfd-lookup](#nfd-lookup) - - [Options](#options-21) + - [Options](#options-23) - [-o, --output ](#-o---output--3) - - [Arguments](#arguments-6) + - [Arguments](#arguments-9) - [VALUE](#value) - [opt-in](#opt-in) - - [Options](#options-22) + - [Options](#options-24) - [-a, --account ](#-a---account-) - [-n, --network ](#-n---network--1) - - [Arguments](#arguments-7) + - [Arguments](#arguments-10) - [ASSET_IDS](#asset_ids) - [opt-out](#opt-out) - - [Options](#options-23) + - [Options](#options-25) - [-a, --account ](#-a---account--1) - [--all](#--all) - [-n, --network ](#-n---network--2) - - [Arguments](#arguments-8) + - [Arguments](#arguments-11) - [ASSET_IDS](#asset_ids-1) - [send](#send) - - [Options](#options-24) + - [Options](#options-26) - [-f, --file ](#-f---file--2) - [-t, --transaction ](#-t---transaction-) - [-n, --network ](#-n---network--3) - [sign](#sign) - - [Options](#options-25) + - [Options](#options-27) - [-a, --account ](#-a---account--2) - [-f, --file ](#-f---file--3) - [-t, --transaction ](#-t---transaction--1) - [-o, --output ](#-o---output--4) - [--force](#--force-2) - [transfer](#transfer) - - [Options](#options-26) + - [Options](#options-28) - [-s, --sender ](#-s---sender-) - [-r, --receiver ](#-r---receiver--1) - [--asset, --id ](#--asset---id-) @@ -167,28 +188,28 @@ - [--whole-units](#--whole-units-2) - [-n, --network ](#-n---network--4) - [vanity-address](#vanity-address) - - [Options](#options-27) + - [Options](#options-29) - [-m, --match ](#-m---match-) - [-o, --output ](#-o---output--5) - [-a, --alias ](#-a---alias-) - [--file-path ](#--file-path-) - [-f, --force](#-f---force) - - [Arguments](#arguments-9) + - [Arguments](#arguments-12) - [KEYWORD](#keyword) - [wallet](#wallet) - - [Options](#options-28) + - [Options](#options-30) - [-a, --address ](#-a---address-) - [-m, --mnemonic](#-m---mnemonic) - [-f, --force](#-f---force-1) - - [Arguments](#arguments-10) + - [Arguments](#arguments-13) - [ALIAS_NAME](#alias_name) - - [Arguments](#arguments-11) + - [Arguments](#arguments-14) - [ALIAS](#alias) - - [Options](#options-29) + - [Options](#options-31) - [-f, --force](#-f---force-2) - - [Arguments](#arguments-12) + - [Arguments](#arguments-15) - [ALIAS](#alias-1) - - [Options](#options-30) + - [Options](#options-32) - [-f, --force](#-f---force-3) # algokit @@ -219,64 +240,48 @@ Force enable or disable of console output styling. ### --skip-version-check Skip version checking and prompting. -## bootstrap - -Expedited initial setup for any developer by installing and configuring dependencies and other -key development environment setup activities. - -```shell -algokit bootstrap [OPTIONS] COMMAND [ARGS]... -``` - -### Options - - -### --force -Continue even if minimum AlgoKit version is not met - -### all +## compile -Runs all bootstrap sub-commands in the current directory and immediate sub directories. +Compile smart contracts and smart signatures written in a supported high-level language +to a format deployable on the Algorand Virtual Machine (AVM). ```shell -algokit bootstrap all [OPTIONS] +algokit compile [OPTIONS] COMMAND [ARGS]... ``` ### Options -### --interactive, --non-interactive, --ci -Enable/disable interactive prompts. If the CI environment variable is set, defaults to non-interactive +### -v, --version +The compiler version to pin to, for example, 1.0.0. If no version is specified, AlgoKit checks if the compiler is installed and runs the installed version. If the compiler is not installed, AlgoKit runs the latest version. If a version is specified, AlgoKit checks if an installed version matches and runs the installed version. Otherwise, AlgoKit runs the specified version. -### env +### py -Copies .env.template file to .env in the current working directory and prompts for any unspecified values. +Compile Algorand Python contract(s) using the PuyaPy compiler. ```shell -algokit bootstrap env [OPTIONS] +algokit compile py [OPTIONS] [PUYAPY_ARGS]... ``` -### Options +### Arguments -### --interactive, --non-interactive, --ci -Enable/disable interactive prompts. If the CI environment variable is set, defaults to non-interactive +### PUYAPY_ARGS +Optional argument(s) -### npm +### python -Runs npm install in the current working directory to install Node.js dependencies. +Compile Algorand Python contract(s) using the PuyaPy compiler. ```shell -algokit bootstrap npm [OPTIONS] +algokit compile python [OPTIONS] [PUYAPY_ARGS]... ``` -### poetry +### Arguments -Installs Python Poetry (if not present) and runs poetry install in the current working directory to install Python dependencies. -```shell -algokit bootstrap poetry [OPTIONS] -``` +### PUYAPY_ARGS +Optional argument(s) ## completions @@ -355,42 +360,6 @@ algokit config version-prompt [OPTIONS] [[enable|disable]] ### ENABLE Optional argument -## deploy - -Deploy smart contracts from AlgoKit compliant repository. - -```shell -algokit deploy [OPTIONS] [ENVIRONMENT_NAME] -``` - -### Options - - -### -C, --command -Custom deploy command. If not provided, will load the deploy command from .algokit.toml file. - - -### --interactive, --non-interactive, --ci -Enable/disable interactive prompts. If the CI environment variable is set, defaults to non-interactive - - -### -P, --path -Specify the project directory. If not provided, current working directory will be used. - - -### --deployer -(Optional) Alias of the deployer account. Otherwise, will prompt the deployer mnemonic if specified in .algokit.toml file. - - -### --dispenser -(Optional) Alias of the dispenser account. Otherwise, will prompt the dispenser mnemonic if specified in .algokit.toml file. - -### Arguments - - -### ENVIRONMENT_NAME -Optional argument - ## dispenser Interact with the AlgoKit TestNet Dispenser. @@ -551,6 +520,10 @@ Programming language of the generated client code python | typescript + +### -v, --version +The client generator version to pin to, for example, 1.0.0. If no version is specified, AlgoKit checks if the client generator is installed and runs the installed version. If the client generator is not installed, AlgoKit runs the latest version. If a version is specified, AlgoKit checks if an installed version matches and runs the installed version. Otherwise, AlgoKit runs the specified version. + ### Arguments @@ -616,7 +589,7 @@ Name of an official template to use. To choose interactively, run this command w * **Options** - tealscript | puya | react | fullstack | beaker | base | playground + tealscript | python | react | fullstack | beaker | base | playground @@ -649,7 +622,7 @@ Whether to open an IDE for you if the IDE and IDE config are detected. Supported ### --workspace, --no-workspace -Whether to prefer structuring standalone projects as part of a workspace. +Whether to prefer structuring standalone projects as part of a workspace. An AlgoKit workspace is a conventional project structure that allows managing multiple standalone projects in a monorepo. ### -a, --answer @@ -747,6 +720,188 @@ Stop the AlgoKit LocalNet. algokit localnet stop [OPTIONS] ``` +## project + +Provides a suite of commands for managing your AlgoKit project. +This includes initializing project dependencies, deploying smart contracts, +and executing predefined or custom commands within your project environment. + +```shell +algokit project [OPTIONS] COMMAND [ARGS]... +``` + +### bootstrap + +Expedited initial setup for any developer by installing and configuring dependencies and other +key development environment setup activities. + +```shell +algokit project bootstrap [OPTIONS] COMMAND [ARGS]... +``` + +### Options + + +### --force +Continue even if minimum AlgoKit version is not met + +#### all + +Runs all bootstrap sub-commands in the current directory and immediate sub directories. + +```shell +algokit project bootstrap all [OPTIONS] +``` + +### Options + + +### --interactive, --non-interactive, --ci +Enable/disable interactive prompts. If the CI environment variable is set, defaults to non-interactive + + +### -p, --project-name +(Optional) Projects to execute the command on. Defaults to all projects found in the current directory. + + +### -t, --type +(Optional) Limit execution to specific project types if executing from workspace. + + +* **Options** + + ProjectType.FRONTEND | ProjectType.CONTRACT | ProjectType.BACKEND + + +#### env + +Copies .env.template file to .env in the current working directory and prompts for any unspecified values. + +```shell +algokit project bootstrap env [OPTIONS] +``` + +### Options + + +### --interactive, --non-interactive, --ci +Enable/disable interactive prompts. If the CI environment variable is set, defaults to non-interactive + +#### npm + +Runs npm install in the current working directory to install Node.js dependencies. + +```shell +algokit project bootstrap npm [OPTIONS] +``` + +#### poetry + +Installs Python Poetry (if not present) and runs poetry install in the current working directory to install Python dependencies. + +```shell +algokit project bootstrap poetry [OPTIONS] +``` + +### deploy + +Deploy smart contracts from AlgoKit compliant repository. + +```shell +algokit project deploy [OPTIONS] [ENVIRONMENT_NAME] +``` + +### Options + + +### -C, --command +Custom deploy command. If not provided, will load the deploy command from .algokit.toml file. + + +### --interactive, --non-interactive, --ci +Enable/disable interactive prompts. Defaults to non-interactive if the CI environment variable is set. Interactive MainNet deployments prompt for confirmation. + + +### -P, --path +Specify the project directory. If not provided, current working directory will be used. + + +### --deployer +(Optional) Alias of the deployer account. Otherwise, will prompt the deployer mnemonic if specified in .algokit.toml file. + + +### --dispenser +(Optional) Alias of the dispenser account. Otherwise, will prompt the dispenser mnemonic if specified in .algokit.toml file. + + +### -p, --project-name +(Optional) Projects to execute the command on. Defaults to all projects found in the current directory. Option is mutually exclusive with command. + +### Arguments + + +### ENVIRONMENT_NAME +Optional argument + +### link + +Automatically invoke 'algokit generate client' on contract projects available in the workspace. +Must be invoked from the root of a standalone 'frontend' typed project. + +```shell +algokit project link [OPTIONS] +``` + +### Options + + +### -p, --project-name +Specify contract projects for the command. Defaults to all in the current workspace. + + +### -l, --language +Programming language of the generated client code + + +* **Options** + + python | typescript + + + +### -a, --all +Link all contract projects with the frontend project Option is mutually exclusive with project_name. + + +### -f, --fail-fast +Exit immediately if at least one client generation process fails + + +### -v, --version +The client generator version to pin to, for example, 1.0.0. If no version is specified, AlgoKit checks if the client generator is installed and runs the installed version. If the client generator is not installed, AlgoKit runs the latest version. If a version is specified, AlgoKit checks if an installed version matches and runs the installed version. Otherwise, AlgoKit runs the specified version. + +### list + +List all projects in the workspace + +```shell +algokit project list [OPTIONS] [WORKSPACE_PATH] +``` + +### Arguments + + +### WORKSPACE_PATH +Optional argument + +### run + +Define custom commands and manage their execution in you projects. + +```shell +algokit project run [OPTIONS] COMMAND [ARGS]... +``` + ## task Collection of useful tasks to help you develop on Algorand. diff --git a/docs/features/compile.md b/docs/features/compile.md new file mode 100644 index 00000000..cea7a3af --- /dev/null +++ b/docs/features/compile.md @@ -0,0 +1,98 @@ +# AlgoKit Compile + +The AlgoKit Compile feature enables you to compile smart contracts (apps) and smart signatures (logic signatures) written in a supported high-level language to a format deployable on the Algorand Virtual Machine (AVM). + +When running the compile command, AlgoKit will take care of working out which compiler you need and dynamically resolve it. Additionally, AlgoKit will detect if a matching compiler version is already installed globally on your machine or is included in your project and use that. + +## Prerequisites + +See [Compile Python - Prerequisites](#prerequisites-1) for details. + +## What is Algorand Python & PuyaPy? + +Algorand Python is a semantically and syntactically compatible, typed Python language that works with standard Python tooling and allows you to express smart contracts (apps) and smart signatures (logic signatures) for deployment on the Algorand Virtual Machine (AVM). + +Algorand Python can be deployed to Algorand by using the PuyaPy optimising compiler, which takes Algorand Python and outputs [ARC-32](https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0032.md) application spec files (among other formats) which, [when deployed](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/generate.md#1-typed-clients), will result in AVM bytecode execution semantics that match the given Python code. + +If you want to learn more, check out the [PuyaPy docs](https://github.com/algorandfoundation/puya/blob/main/docs/index.md). + +Below is an example Algorand Python smart contract. + +```py +from algopy import ARC4Contract, arc4 + +class HelloWorldContract(ARC4Contract): + @arc4.abimethod + def hello(self, name: arc4.String) -> arc4.String: + return "Hello, " + name +``` + +For more complex examples, see the [examples](https://github.com/algorandfoundation/puya/tree/main/examples) in the [PuyaPy repo](https://github.com/algorandfoundation/puya). + +## Usage + +Available commands and possible usage are as follows: + +``` +Usage: algokit compile [OPTIONS] COMMAND [ARGS]... + + Compile smart contracts and smart signatures written in a supported high-level language to a format deployable on + the Algorand Virtual Machine (AVM). + +Options: + -v, --version TEXT The compiler version to pin to, for example, 1.0.0. If no version is specified, AlgoKit checks + if the compiler is installed and runs the installed version. If the compiler is not installed, + AlgoKit runs the latest version. If a version is specified, AlgoKit checks if an installed + version matches and runs the installed version. Otherwise, AlgoKit runs the specified version. + -h, --help Show this message and exit. + +Commands: + py Compile Algorand Python contract(s) using the PuyaPy compiler. + python Compile Algorand Python contract(s) using the PuyaPy compiler. +``` + +### Compile Python + +The command `algokit compile python` or `algokit compile py` will run the [PuyaPy](https://github.com/algorandfoundation/puya) compiler against the supplied Algorand Python smart contract. + +All arguments supplied to the command are passed directly to PuyaPy, therefore this command supports all options supported by the PuyaPy compiler. + +Any errors detected by PuyaPy during the compilation process will be printed to the output. + +#### Prerequisites + +PuyaPy requires Python 3.12+, so please ensure your Python version satisfies this requirement. + +This command will attempt to resolve a matching installed PuyaPy compiler, either globally installed in the system or locally installed in your project (via [Poetry](https://python-poetry.org/)). If no appropriate match is found, the PuyaPy compiler will be dynamically run using [pipx](https://pipx.pypa.io/stable/). In this case pipx is also required. + +#### Examples + +To see a list of the supported PuyaPy options, run the following: + +```shell +algokit compile python -h +``` + +To determine the version of the PuyaPy compiler in use, execute the following command: + +```shell +algokit compile python --version +``` + +To compile a single Algorand Python smart contract and write the output to a specific location, run the following: + +```shell +algokit compile python hello_world/contract.py --out-dir hello_world/out +``` + +To compile multiple Algorand Python smart contracts and write the output to a specific location, run the following: + +```shell +algokit compile python hello_world/contract.py calculator/contract.py --out-dir my_contracts +``` + +To compile a directory of Algorand Python smart contracts and write the output to the default location, run the following: + +```shell +algokit compile python my_contracts +``` diff --git a/docs/features/generate.md b/docs/features/generate.md index 15719aa5..f44ad0b1 100644 --- a/docs/features/generate.md +++ b/docs/features/generate.md @@ -8,13 +8,10 @@ The `algokit generate client` [command](../cli/index.md#client) can be used to g ### Prerequisites -To generate Python clients AlgoKit itself is the only dependency. +To generate Python clients an installation of pip and pipx is required. To generate TypeScript clients an installation of Node.js and npx is also required. -Each generated client will also have a dependency on `algokit-utils` libraries for the target language: - -- Python clients require: `algokit-utils@^1.2` -- TypeScript clients require: `@algorandfoundation/algokit-utils@^2.0` +Each generated client will also have a dependency on `algokit-utils` libraries for the target language. ### Input file / directory @@ -30,6 +27,12 @@ There are two tokens available for use with the `-o`, `--output` [option](../cli - `{contract_name}`: This will resolve to a name based on the ARC-0032 contract name, formatted appropriately for the target language. - `{app_spec_dir}`: This will resolve to the parent directory of the `application.json` or `*.arc32.json` file which can be useful to output a client relative to its source file. +### Version Pinning + +If you want to ensure typed client output stability across different environments and additionally protect yourself from any potential breaking changes introduced in the client generator packages, you can specify a version you'd like to pin to. + +To make use of this feature, pass `-v`, `--version`, for example `algokit generate client --version 1.2.3 path/to/application.json`. + ### Usage Usage examples of using a generated client are below, typed clients allow your favourite IDE to provide better intellisense to provide better discoverability diff --git a/docs/features/init.md b/docs/features/init.md index c6495be7..362a4311 100644 --- a/docs/features/init.md +++ b/docs/features/init.md @@ -20,7 +20,7 @@ $ ~ algokit init ? Name of project / directory to create the project in: my-cool-app ``` -Once above 2 questions are answered, the `cli` will start instantiating the project and will start asking questions specific to the template you are instantiating. By default official templates such as `puya`, `fullstack`, `react`, `beaker` include a notion of a `preset`. If you want to skip all questions and let the tool preset the answers tailored for a starter project you can pick `Starter`, for a more advanced project that includes unit tests, CI automation and other advanced features, pick `Production`. Lastly, if you prefer to modify the experience and tailor the template to your needs, pick the `Custom` preset. +Once above 2 questions are answered, the `cli` will start instantiating the project and will start asking questions specific to the template you are instantiating. By default official templates such as `python`, `fullstack`, `react`, `beaker` include a notion of a `preset`. If you want to skip all questions and let the tool preset the answers tailored for a starter project you can pick `Starter`, for a more advanced project that includes unit tests, CI automation and other advanced features, pick `Production`. Lastly, if you prefer to modify the experience and tailor the template to your needs, pick the `Custom` preset. If you want to accept the default for each option simply hit [enter] or alternatively to speed things up you can run `algokit init --defaults` and they will be auto-accepted. @@ -42,6 +42,26 @@ type = 'workspace' # type specifying if the project is a workspace or standalone projects_root_path = 'projects' # path to the root folder containing all sub-projects in the workspace ``` +#### VSCode optimizations + +AlgoKit has a set of minor optimizations for VSCode users that are useful to be aware of: + +- Templates created with the `--workspace` flag automatically include a VSCode code-workspace file. New projects added to an AlgoKit workspace are also integrated into an existing VSCode workspace. +- Using the `--ide` flag with `init` triggers automatic prompts to open the project and, if available, the code workspace in VSCode. + +#### Handling of the `.github` Folder + +A key aspect of using the `--workspace` flag is how the `.github` folder is managed. This folder, which contains GitHub-specific configurations such as workflows and issue templates, is moved from the project directory to the root of the workspace. This move is necessary because GitHub does not recognize workflows located in subdirectories. + +Here's a simplified overview of what happens: + +1. If a `.github` folder is found in your project, its contents are transferred to the workspace's root `.github` folder. +2. Files with matching names in the destination are not overwritten; they're skipped. +3. The original `.github` folder is removed if it's left empty after the move. +4. A notification is displayed, advising you to review the moved `.github` contents to ensure everything is in order. + +This process ensures that your GitHub configurations are properly recognized at the workspace level, allowing you to utilize GitHub Actions and other features seamlessly across your projects. + ### Standalone Projects Standalone projects are suitable for simpler applications or when working on a single component. This structure is straightforward, with each project residing in its own directory, independent of others. Standalone projects are ideal for developers who prefer simplicity or are focusing on a single aspect of their application and are sure that they will not need to add more sub-projects in the future. @@ -135,7 +155,7 @@ By combining a number of options, it is possible to initialize a new project wit ``` -$ ~ algokit init -n my-smart-contract -t puya --no-git --no-bootstrap --answer author_name "Algorand Foundation" --defaults +$ ~ algokit init -n my-smart-contract -t python --no-git --no-bootstrap --answer author_name "Algorand Foundation" --defaults 🙌 Project initialized at `my-smart-contract`! For template specific next steps, consult the documentation of your selected template 🧐 Your selected template comes from: ➡ī¸ https://github.com/algorandfoundation/algokit-beaker-default-template @@ -146,15 +166,3 @@ As a suggestion, if you wanted to open the project in VS Code you could execute: ``` For more details about the `AlgoKit init` command, please refer to the [AlgoKit CLI reference documentation](../cli/index.md#init). - -``` - -``` - -``` - -``` - -``` - -``` diff --git a/docs/features/project.md b/docs/features/project.md new file mode 100644 index 00000000..587e72a9 --- /dev/null +++ b/docs/features/project.md @@ -0,0 +1,9 @@ +# AlgoKit Project + +`algokit project` is a collection of commands and command groups useful for managing algokit compliant projects. + +## Features + +- [bootstrap](./project/bootstrap.md) - Bootstrap your project with AlgoKit. +- [deploy](./project/deploy.md) - Deploy your project to Algorand blockchain. +- [run](./project/project.md) - Define custom commands and manage their execution via `algokit` cli. diff --git a/docs/features/bootstrap.md b/docs/features/project/bootstrap.md similarity index 54% rename from docs/features/bootstrap.md rename to docs/features/project/bootstrap.md index 6eb5bdf7..a1c164ba 100644 --- a/docs/features/bootstrap.md +++ b/docs/features/project/bootstrap.md @@ -1,6 +1,6 @@ -# AlgoKit Bootstrap +# AlgoKit Project Bootstrap -The AlgoKit Bootstrap feature allows you to bootstrap different project dependencies by looking up specific files in your current directory and immediate sub directories by convention. +The AlgoKit Project Bootstrap feature allows you to bootstrap different project dependencies by looking up specific files in your current directory and immediate sub directories by convention. This is useful to allow for expedited initial setup for each developer e.g. when they clone a repository for the first time. It's also useful to provide a quick getting started experience when initialising a new project via [AlgoKit Init](./init.md) and meeting our goal of "nothing to debugging code in 5 minutes". @@ -10,13 +10,15 @@ It can bootstrap one or all of the following (with other options potentially bei - Node.js project - Checks if npm is installed and runs `npm install` - dotenv (.env) file - Checks for `.env.template` files, copies them to `.env` (which should be in `.gitignore` so developers can safely make local specific changes) and prompts for any blank values (so the developer has an easy chance to fill in their initial values where there isn't a clear default). +> **Note**: Invoking bootstrap from `algokit bootstrap` is not recommended. Please prefer using `algokit project bootstrap` instead. + ## Usage Available commands and possible usage as follows: ``` -$ ~ algokit bootstrap -Usage: algokit bootstrap [OPTIONS] COMMAND [ARGS]... +$ ~ algokit project bootstrap +Usage: algokit project bootstrap [OPTIONS] COMMAND [ARGS]... Options: -h, --help Show this message and exit. @@ -32,7 +34,7 @@ Commands: ### Bootstrap .env file -The command `algokit bootstrap env` runs two main tasks in the current directory: +The command `algokit project bootstrap env` runs two main tasks in the current directory: - Searching for `.env.template` file in the current directory and use it as template to create a new `.env` file in the same directory. - Prompting the user to enter a value for any empty token values in the `env.` including printing the comments above that empty token @@ -46,10 +48,10 @@ SERVER_URL=https://myserver.com SERVER_PORT= ``` -Running the `algokit bootstrap env` command while the above `.env.template` file in the current directory will result in the following: +Running the `algokit project bootstrap env` command while the above `.env.template` file in the current directory will result in the following: ``` -$ ~ algokit bootstrap env +$ ~ algokit project bootstrap env Copying /Users/me/my-project/.env.template to /Users/me/my-project/.env and prompting for empty values # This is a mandatory field to run the server, please enter a value value # For example: 5000 @@ -68,12 +70,12 @@ SERVER_PORT=4000 ### Bootstrap Node.js project -The command `algokit bootstrap npm` installs Node.js project dependencies if there is a `package.json` file in the current directory by running `npm install` command to install all node modules specified in that file. If you don't have npm available it will show a clear error message and resolution instructions. +The command `algokit project bootstrap npm` installs Node.js project dependencies if there is a `package.json` file in the current directory by running `npm install` command to install all node modules specified in that file. If you don't have npm available it will show a clear error message and resolution instructions. -Here is an example outcome of running `algokit bootstrap npm` command: +Here is an example outcome of running `algokit project bootstrap npm` command: ``` -$ ~ algokit bootstrap npm +$ ~ algokit project bootstrap npm Installing npm dependencies npm: npm: added 17 packages, and audited 18 packages in 3s @@ -86,15 +88,15 @@ npm: found 0 vulnerabilities ### Bootstrap Python poetry project -The command `algokit bootstrap poetry` does two main actions: +The command `algokit project bootstrap poetry` does two main actions: - Checking for Poetry version by running `poetry --version` and upgrades it if required - Installing Python dependencies and setting up Python virtual environment via Poetry in the current directory by running `poetry install`. -Here is an example of running `algokit bootstrap poetry` command: +Here is an example of running `algokit project bootstrap poetry` command: ``` -$ ~ algokit bootstrap poetry +$ ~ algokit project bootstrap poetry Installing Python dependencies and setting up Python virtual environment via Poetry poetry: poetry: Installing dependencies from lock file @@ -109,6 +111,18 @@ poetry: Installing the current project: algokit (0.1.0) ### Bootstrap all -You can run `algokit bootstrap all` which will run all three commands `algokit bootstrap env`, `algokit bootstrap npm` and `algokit bootstrap poetry` inside the current directory and all immediate sub-directories. This command is executed by default after initialising a new project via the [AlgoKit Init](./init.md) command. +Execute `algokit project bootstrap all` to initiate `algokit project bootstrap env`, `algokit project bootstrap npm`, and `algokit project bootstrap poetry` commands within the current directory and all its immediate sub-directories. This comprehensive command is automatically triggered following the initialization of a new project through the [AlgoKit Init](./init.md) command. + +#### Filtering Options + +The `algokit project bootstrap all` command includes flags for more granular control over the bootstrapping process within [AlgoKit workspaces](../init.md#workspaces): + +- `--project-name`: This flag allows you to specify one or more project names to bootstrap. Only projects matching the provided names will be bootstrapped. This is particularly useful in monorepos or when working with multiple projects in the same directory structure. + +- `--type`: Use this flag to limit the bootstrapping process to projects of a specific type (e.g., `frontend`, `backend`, `contract`). This option streamlines the setup process by focusing on relevant project types, reducing the overall bootstrapping time. + +These new flags enhance the flexibility and efficiency of the bootstrapping process, enabling developers to tailor the setup according to project-specific needs. + +## Further Reading -To learn more about the `algokit bootstrap` command, please refer to [bootstrap](../cli/index.md#bootstrap) in the AlgoKit CLI reference documentation. +To learn more about the `algokit project bootstrap` command, please refer to [bootstrap](../../cli/index.md#bootstrap) in the AlgoKit CLI reference documentation. diff --git a/docs/features/deploy.md b/docs/features/project/deploy.md similarity index 63% rename from docs/features/deploy.md rename to docs/features/project/deploy.md index 3c86e6a9..ae703124 100644 --- a/docs/features/deploy.md +++ b/docs/features/project/deploy.md @@ -1,11 +1,13 @@ -# AlgoKit Deploy Feature Documentation +# AlgoKit Project Deploy -Deploy your smart contracts effortlessly to various networks with the AlgoKit Deploy feature. This feature is essential for automation in CI/CD pipelines and for seamless deployment to various Algorand network environments. +Deploy your smart contracts effortlessly to various networks with the algokit project deploy feature. This feature is essential for automation in CI/CD pipelines and for seamless deployment to various Algorand network environments. + +> **Note**: Invoking deploy from `algokit deploy` is not recommended. Please prefer using `algokit project deploy` instead. ## Usage ```sh -$ algokit deploy [OPTIONS] [ENVIRONMENT_NAME] +$ algokit project deploy [OPTIONS] [ENVIRONMENT_NAME] ``` This command deploys smart contracts from an AlgoKit compliant repository to the specified network. @@ -17,6 +19,8 @@ This command deploys smart contracts from an AlgoKit compliant repository to the - `--path, -P DIRECTORY`: Specifies the project directory. If not provided, the current working directory will be used. - `--deployer`: Specifies the deployer alias. If not provided and if the deployer is specified in `.algokit.toml` file its mnemonic will be prompted. - `--dispenser`: Specifies the dispenser alias. If not provided and if the dispenser is specified in `.algokit.toml` file its mnemonic will be prompted. +- `-p, --project-name`: (Optional) Projects to execute the command on. Defaults to all projects found in + the current directory. Option is mutually exclusive with `--command`. - `-h, --help`: Show this message and exit. ## Environment files @@ -54,23 +58,27 @@ Here's an example of what the `.algokit.toml` file might look like. When deployi [algokit] min_version = "v{lastest_version}" -[deploy] +[project] + +... # project configuration and custom commands + +[project.deploy] command = "poetry run python -m smart_contracts deploy" environment_secrets = [ "DEPLOYER_MNEMONIC", ] -[deploy.localnet] +[project.deploy.localnet] environment_secrets = [] ``` -The `command` key under each `[deploy.{network_name}]` section should contain a string that represents the deployment command for that particular network. If a `command` key is not provided in a network-specific section, the command from the general `[deploy]` section will be used. +The `command` key under each `[project.deploy.{network_name}]` section should contain a string that represents the deployment command for that particular network. If a `command` key is not provided in a network-specific section, the command from the general `[project.deploy]` section will be used. -The `environment_secrets` key should contain a list of names of environment variables that should be treated as secrets. This can be defined in the general `[deploy]` section, as well as in the network-specific sections. The environment-specific secrets will be added to the general secrets during deployment. +The `environment_secrets` key should contain a list of names of environment variables that should be treated as secrets. This can be defined in the general `[project.deploy]` section, as well as in the network-specific sections. The environment-specific secrets will be added to the general secrets during deployment. The `[algokit]` section with the `min_version` key allows you to specify the minimum version of AlgoKit that the project requires. -This way, you can define common deployment logic and environment secrets in the `[deploy]` section, and provide overrides or additions for specific environments in the `[deploy.{environment_name}]` sections. +This way, you can define common deployment logic and environment secrets in the `[project.deploy]` section, and provide overrides or additions for specific environments in the `[project.deploy.{environment_name}]` sections. ## Deploying to a Specific Network @@ -79,11 +87,42 @@ The command requires a `ENVIRONMENT` argument, which specifies the network envir Example: ```sh -$ algokit deploy testnet +$ algokit project deploy testnet ``` This command deploys the smart contracts to the testnet. +## Deploying to a Specific Network from a workspace with project name filter + +The command requires a `ENVIRONMENT` argument, which specifies the network environment to which the smart contracts will be deployed. Please note, the `environment` argument is case-sensitive. + +Example: + +Root `.algokit.toml`: + +```toml +[project] +type = "workspace" +projects_root_dir = 'projects' +``` + +Contract project `.algokit.toml`: + +```toml +[project] +type = "contract" +name = "myproject" + +[project.deploy] +command = "{custom_deploy_command}" +``` + +```bash +$ algokit project deploy testnet --project-name myproject +``` + +This command deploys the smart contracts to TestNet from a sub project named 'myproject', which is available within the current workspace. All `.env` loading logic described in [Environment files](#environment-files) is applicable, execution from the workspace root orchestrates invoking the deploy command from the working directory of each applicable sub project. + ## Custom Project Directory By default, the deploy command looks for the `.algokit.toml` file in the current working directory. You can specify a custom project directory using the `--project-dir` option. @@ -91,7 +130,7 @@ By default, the deploy command looks for the `.algokit.toml` file in the current Example: ```sh -$ algokit deploy testnet --project-dir="path/to/project" +$ algokit project deploy testnet --project-dir="path/to/project" ``` ## Custom Deploy Command @@ -101,9 +140,11 @@ You can provide a custom deploy command using the `--custom-deploy-command` opti Example: ```sh -$ algokit deploy testnet --custom-deploy-command="your-custom-command" +$ algokit project deploy testnet --custom-deploy-command="your-custom-command" ``` +> ⚠ī¸ Please note, chaining multiple commands with `&&` is **not** currently supported. If you need to run multiple commands, you can defer to a custom script. Refer to [run](../project/run.md#custom-command-injection) for scenarios where multiple sub-command invocations are required. + ## CI Mode By using the `--ci` or `--non-interactive` flag, you can skip the interactive prompt for mnemonics. @@ -113,17 +154,17 @@ This is useful in CI/CD environments where user interaction is not possible. Whe Example: ```sh -$ algokit deploy testnet --ci +$ algokit project deploy testnet --ci ``` ## Example of a Full Deployment ```sh -$ algokit deploy testnet --custom-deploy-command="your-custom-command" +$ algokit project deploy testnet --custom-deploy-command="your-custom-command" ``` This example shows how to deploy smart contracts to the testnet using a custom deploy command. This also assumes that .algokit.toml file is present in the current working directory, and .env.testnet file is present in the current working directory and contains the required environment variables for deploying to TestNet environment. ## Further Reading -For in-depth details, visit the [deploy](../cli/index.md#deploy) section in the AlgoKit CLI reference documentation. +For in-depth details, visit the [deploy](../../cli/index.md#deploy) section in the AlgoKit CLI reference documentation. diff --git a/docs/features/project/link.md b/docs/features/project/link.md new file mode 100644 index 00000000..6d5f9c1a --- /dev/null +++ b/docs/features/project/link.md @@ -0,0 +1,67 @@ +# AlgoKit Project Link Command + +The `algokit project link` command is a powerful feature designed to streamline the integration between `frontend` and `contract` typed projects within the AlgoKit ecosystem. This command facilitates the automatic path resolution and invocation of [`algokit generate client`](../generate.md#1-typed-clients) on `contract` projects available in the workspace, making it easier to integrate smart contracts with frontend applications. + +## Usage + +To use the `link` command, navigate to the root directory of your standalone frontend project and execute: + +```sh +$ algokit project link [OPTIONS] +``` + +This command must be invoked from the root of a standalone 'frontend' typed project. + +## Options + +- `--project-name`, `-p`: Specify one or more contract projects for the command. If not provided, the command defaults to all contract projects in the current workspace. This option can be repeated to specify multiple projects. + +- `--language`, `-l`: Set the programming language of the generated client code. The default is `typescript`, but you can specify other supported languages as well. + +- `--all`, `-a`: Link all contract projects with the frontend project. This option is mutually exclusive with `--project-name`. + +- `--fail-fast`, `-f`: Exit immediately if at least one client generation process fails. This is useful for CI/CD pipelines where you want to ensure all clients are correctly generated before proceeding. + +- `--version`, `-v`: Allows specifying the version of the client generator to use when generating client code for contract projects. This can be particularly useful for ensuring consistency across different environments or when a specific version of the client generator includes features or fixes that are necessary for your project. + +## How It Works + +Below is a visual representation of the `algokit project link` command in action: + +```mermaid +graph LR + F[Frontend Project] -->|algokit generate client| C1[Contract Project 1] + F -->|algokit generate client| C2[Contract Project 2] + F -->|algokit generate client| CN[Contract Project N] + + C1 -->|algokit generate client| F + C2 -->|algokit generate client| F + CN -->|algokit generate client| F + + classDef frontend fill:#f9f,stroke:#333,stroke-width:4px; + classDef contract fill:#bbf,stroke:#333,stroke-width:2px; + class F frontend; + class C1,C2,CN contract; +``` + +1. **Project Type Verification**: The command first verifies that it is being executed within a standalone frontend project by checking the project's type in the `.algokit.toml` configuration file. + +2. **Contract Project Selection**: Based on the provided options, it selects the contract projects to link. This can be all contract projects within the workspace, a subset specified by name, or a single project selected interactively. + +3. **Client Code Generation**: For each selected contract project, it generates typed client code using the specified language. The generated code is placed in the frontend project's directory specified for contract clients. + +4. **Feedback**: The command provides feedback for each contract project it processes, indicating success or failure in generating the client code. + +## Example + +Linking all contract projects with a frontend project and generating TypeScript clients: + +```sh +$ algokit project link --all -l typescript +``` + +This command will generate TypeScript clients for all contract projects and place them in the specified directory within the frontend project. + +## Further Reading + +For more details on configuring your projects for optimal use with the `algokit project link` command, refer to the documentation on [AlgoKit Project Configuration](docs/features/project/config.md) and [AlgoKit Frontend Projects](docs/features/project/frontend.md). diff --git a/docs/features/project/list.md b/docs/features/project/list.md new file mode 100644 index 00000000..1e424047 --- /dev/null +++ b/docs/features/project/list.md @@ -0,0 +1,43 @@ +# AlgoKit Project List Command + +The `algokit project list` command is designed to enumerate all projects within an AlgoKit workspace. This command is particularly useful in workspace environments where multiple projects are managed under a single root directory. It provides a straightforward way to view all the projects that are part of the workspace. + +## Usage + +To use the `list` command, execute the following **anywhere** within an AlgoKit workspace: + +```sh +$ algokit project list [OPTIONS] [WORKSPACE_PATH] +``` + +- `WORKSPACE_PATH` is an optional argument that specifies the path to the workspace. If not provided, the current directory (`.`) is used as the default workspace path. + +## How It Works + +1. **Workspace Verification**: Initially, the command checks if the specified directory (or the current directory by default) is an AlgoKit workspace. This is determined by looking for a `.algokit.toml` configuration file and verifying if the `project.type` is set to `workspace`. + +2. **Project Enumeration**: If the directory is confirmed as a workspace, the command proceeds to enumerate all projects within the workspace. This is achieved by scanning the workspace's subdirectories for `.algokit.toml` files and extracting project names. + +3. **Output**: The names of all discovered projects are printed to the console. If the `-v` or `--verbose` option is used, additional details about each project are displayed. + +## Example Output + +```sh +workspace: {path_to_workspace} 📁 + - myapp ({path_to_myapp}) 📜 + - myproject-app ({path_to_myproject_app}) đŸ–Ĩī¸ +``` + +## Error Handling + +If the command is executed in a directory that is not recognized as an AlgoKit workspace, it will issue a warning: + +```sh +WARNING: No AlgoKit workspace found. Check [project.type] definition at .algokit.toml +``` + +This message indicates that either the current directory does not contain a `.algokit.toml` file or the `project.type` within the file is not set to `workspace`. + +## Further Reading + +For more information on working with AlgoKit workspaces and projects, refer to the [AlgoKit Project Run](docs/features/project/run.md) and [AlgoKit Project Bootstrap](docs/features/project/bootstrap.md) documentation. diff --git a/docs/features/project/run.md b/docs/features/project/run.md new file mode 100644 index 00000000..b1499e72 --- /dev/null +++ b/docs/features/project/run.md @@ -0,0 +1,140 @@ +# AlgoKit Project Run + +The `algokit project run` command allows defining custom commands to execute at standalone project level or being orchestrated from a workspace containing multiple standalone projects. + +## Usage + +```sh +$ algokit project run [OPTIONS] COMMAND [ARGS] +``` + +This command executes a custom command defined in the `.algokit.toml` file of the current project or workspace. + +### Workspace vs Standalone Projects + +AlgoKit supports two main types of project structures: Workspaces and Standalone Projects. This flexibility caters to the diverse needs of developers, whether managing multiple related projects or focusing on a single application. + +- **Workspaces**: Ideal for complex applications comprising multiple sub-projects. Workspaces facilitate organized management of these sub-projects under a single root directory, streamlining dependency management and shared configurations. + +- **Standalone Projects**: Suited for simpler applications or when working on a single component. This structure offers straightforward project management, with each project residing in its own directory, independent of others. + +> Please note, instantiating a workspace inside a workspace (aka 'workspace nesting') is not supported and not recommended. When you want to add a new project into existing workspace make sure to run `algokit init` **from the root of the workspace** + +### Custom Command Injection + +AlgoKit enhances project automation by allowing the injection of custom commands into the `.algokit.toml` configuration file. This feature enables developers to tailor the project setup to their specific needs, automating tasks such as deploying to different network environments or integrating with CI/CD pipelines. + +## How It Works + +The orchestration between workspaces, standalone projects, and custom commands is designed to provide a seamless development experience. Below is a high-level overview of how these components interact within the AlgoKit ecosystem. + +```mermaid +graph TD; +A[AlgoKit Project] --> B["Workspace (.algokit.toml)"]; +A --> C["Standalone Project (.algokit.toml)"]; +B --> D["Sub-Project 1 (.algokit.toml)"]; +B --> E["Sub-Project 2 (.algokit.toml)"]; +C --> F["Custom Commands defined in .algokit.toml"]; +D --> F; +E --> F; +``` + +- **AlgoKit Project**: The root command that encompasses all project-related functionalities. +- **Workspace**: A root folder that is managing multiple related sub-projects. +- **Standalone Project**: An isolated project structure for simpler applications. +- **Custom Commands**: Commands defined by the user in the `.algokit.toml` and automatically injected into the `algokit project run` command group. + +### Workspace cli options + +Below is only visible and available when running from a workspace root. + +- `-l, --list`: List all projects associated with the workspace command. (Optional) +- `-p, --project-name`: Execute the command on specified projects. Defaults to all projects in the current directory. (Optional) +- `-t, --type`: Limit execution to specific project types if executing from workspace. (Optional) + To get a detailed help on the above commands execute: + +```bash +algokit project run {name_of_your_command} --help +``` + +## Examples + +Assume you have a default workspace with the following structure: + +```bash +my_workspace +├── .algokit.toml +├── projects +│ ├── project1 +│ │ └── .algokit.toml +│ └── project2 +│ └── .algokit.toml +``` + +The workspace configuration file is defined as follows: + +```toml +# ... other non [project.run] related metadata +[project] +type = 'workspace' +projects_root_path = 'projects' +# ... other non [project.run] related metadata +``` + +Standalone configuration files are defined as follows: + +```toml +# ... other non [project.run] related metadata + +[project] +type = 'contract' +name = 'project_a' + +[project.run] +hello = { commands = ['echo hello'], description = 'Prints hello' } + +# ... other non [project.run] related metadata +``` + +```toml +# ... other non [project.run] related metadata + +[project] +type = 'frontend' +name = 'project_b' + +[project.run] +hello = { commands = ['echo hello'], description = 'Prints hello' } + +# ... other non [project.run] related metadata +``` + +Executing `algokit project run hello` from the root of the workspace will concurrently execute `echo hello` in both `project_a` and `project_b` directories. + +Executing `algokit project run hello` from the root of `project_(a|b)` will execute `echo hello` in the `project_(a|b)` directory. + +### Controlling order of execution + +To control order of execution, simply define the order for a particular command as follows: + +```yaml +# ... other non [project.run] related metadata +[project] +type = 'workspace' +projects_root_path = 'projects' + +[project.run] +hello = ['project_a', 'project_b'] +# ... other non [project.run] related metadata +``` + +Now if project_a and project_b are both defined as standalone projects, the order of execution will be respected. Additional behaviour can be described as follows: + +- Providing invalid project names will skip the execution of the command for the invalid project names. +- If only a subset of projects declaring this command are specified, the order of execution will be respected for the subset of projects first before the rest of the projects are executed in non-deterministic order. + +> Please note, when enabling explicit order of execution all commands will always run sequentially. + +## Further Reading + +For in-depth details, visit the [run](https://github.com/algorandfoundation/algokit-cli/blob/feat/command_orchestration/docs/features/project/run.md) section in the AlgoKit CLI reference documentation. diff --git a/docs/imgs/algokit-map.png b/docs/imgs/algokit-map.png index b491631b..fc54b4ff 100644 Binary files a/docs/imgs/algokit-map.png and b/docs/imgs/algokit-map.png differ diff --git a/docs/tutorials/algokit-template.md b/docs/tutorials/algokit-template.md index 178f15fd..fbd590a1 100644 --- a/docs/tutorials/algokit-template.md +++ b/docs/tutorials/algokit-template.md @@ -222,7 +222,3 @@ This should dynamically load and display your generator as an optional `cli` com Creating custom templates in AlgoKit is a powerful way to streamline your development workflow for Algorand smart contracts, whether you are using Python or TypeScript. Leveraging Copier and Jinja for templating, and incorporating best practices for modularity, documentation, and coding standards, can result in robust, flexible, and user-friendly templates that can be a valuable asset to both your own projects and the broader Algorand community. Happy coding! - -``` - -``` diff --git a/poetry.lock b/poetry.lock index dd91eb6c..86cb59b1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,87 +2,87 @@ [[package]] name = "aiohttp" -version = "3.9.2" +version = "3.9.3" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:772fbe371788e61c58d6d3d904268e48a594ba866804d08c995ad71b144f94cb"}, - {file = "aiohttp-3.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:edd4f1af2253f227ae311ab3d403d0c506c9b4410c7fc8d9573dec6d9740369f"}, - {file = "aiohttp-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cfee9287778399fdef6f8a11c9e425e1cb13cc9920fd3a3df8f122500978292b"}, - {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc158466f6a980a6095ee55174d1de5730ad7dec251be655d9a6a9dd7ea1ff9"}, - {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54ec82f45d57c9a65a1ead3953b51c704f9587440e6682f689da97f3e8defa35"}, - {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abeb813a18eb387f0d835ef51f88568540ad0325807a77a6e501fed4610f864e"}, - {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc91d07280d7d169f3a0f9179d8babd0ee05c79d4d891447629ff0d7d8089ec2"}, - {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b65e861f4bebfb660f7f0f40fa3eb9f2ab9af10647d05dac824390e7af8f75b7"}, - {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:04fd8ffd2be73d42bcf55fd78cde7958eeee6d4d8f73c3846b7cba491ecdb570"}, - {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3d8d962b439a859b3ded9a1e111a4615357b01620a546bc601f25b0211f2da81"}, - {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:8ceb658afd12b27552597cf9a65d9807d58aef45adbb58616cdd5ad4c258c39e"}, - {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0e4ee4df741670560b1bc393672035418bf9063718fee05e1796bf867e995fad"}, - {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2dec87a556f300d3211decf018bfd263424f0690fcca00de94a837949fbcea02"}, - {file = "aiohttp-3.9.2-cp310-cp310-win32.whl", hash = "sha256:3e1a800f988ce7c4917f34096f81585a73dbf65b5c39618b37926b1238cf9bc4"}, - {file = "aiohttp-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:ea510718a41b95c236c992b89fdfc3d04cc7ca60281f93aaada497c2b4e05c46"}, - {file = "aiohttp-3.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6aaa6f99256dd1b5756a50891a20f0d252bd7bdb0854c5d440edab4495c9f973"}, - {file = "aiohttp-3.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a27d8c70ad87bcfce2e97488652075a9bdd5b70093f50b10ae051dfe5e6baf37"}, - {file = "aiohttp-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:54287bcb74d21715ac8382e9de146d9442b5f133d9babb7e5d9e453faadd005e"}, - {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb3d05569aa83011fcb346b5266e00b04180105fcacc63743fc2e4a1862a891"}, - {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8534e7d69bb8e8d134fe2be9890d1b863518582f30c9874ed7ed12e48abe3c4"}, - {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4bd9d5b989d57b41e4ff56ab250c5ddf259f32db17159cce630fd543376bd96b"}, - {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa6904088e6642609981f919ba775838ebf7df7fe64998b1a954fb411ffb4663"}, - {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bda42eb410be91b349fb4ee3a23a30ee301c391e503996a638d05659d76ea4c2"}, - {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:193cc1ccd69d819562cc7f345c815a6fc51d223b2ef22f23c1a0f67a88de9a72"}, - {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b9f1cb839b621f84a5b006848e336cf1496688059d2408e617af33e3470ba204"}, - {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:d22a0931848b8c7a023c695fa2057c6aaac19085f257d48baa24455e67df97ec"}, - {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4112d8ba61fbd0abd5d43a9cb312214565b446d926e282a6d7da3f5a5aa71d36"}, - {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c4ad4241b52bb2eb7a4d2bde060d31c2b255b8c6597dd8deac2f039168d14fd7"}, - {file = "aiohttp-3.9.2-cp311-cp311-win32.whl", hash = "sha256:ee2661a3f5b529f4fc8a8ffee9f736ae054adfb353a0d2f78218be90617194b3"}, - {file = "aiohttp-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:4deae2c165a5db1ed97df2868ef31ca3cc999988812e82386d22937d9d6fed52"}, - {file = "aiohttp-3.9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:6f4cdba12539215aaecf3c310ce9d067b0081a0795dd8a8805fdb67a65c0572a"}, - {file = "aiohttp-3.9.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:84e843b33d5460a5c501c05539809ff3aee07436296ff9fbc4d327e32aa3a326"}, - {file = "aiohttp-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8008d0f451d66140a5aa1c17e3eedc9d56e14207568cd42072c9d6b92bf19b52"}, - {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61c47ab8ef629793c086378b1df93d18438612d3ed60dca76c3422f4fbafa792"}, - {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc71f748e12284312f140eaa6599a520389273174b42c345d13c7e07792f4f57"}, - {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a1c3a4d0ab2f75f22ec80bca62385db2e8810ee12efa8c9e92efea45c1849133"}, - {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a87aa0b13bbee025faa59fa58861303c2b064b9855d4c0e45ec70182bbeba1b"}, - {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2cc0d04688b9f4a7854c56c18aa7af9e5b0a87a28f934e2e596ba7e14783192"}, - {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1956e3ac376b1711c1533266dec4efd485f821d84c13ce1217d53e42c9e65f08"}, - {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:114da29f39eccd71b93a0fcacff178749a5c3559009b4a4498c2c173a6d74dff"}, - {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3f17999ae3927d8a9a823a1283b201344a0627272f92d4f3e3a4efe276972fe8"}, - {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:f31df6a32217a34ae2f813b152a6f348154f948c83213b690e59d9e84020925c"}, - {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7a75307ffe31329928a8d47eae0692192327c599113d41b278d4c12b54e1bd11"}, - {file = "aiohttp-3.9.2-cp312-cp312-win32.whl", hash = "sha256:972b63d589ff8f305463593050a31b5ce91638918da38139b9d8deaba9e0fed7"}, - {file = "aiohttp-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:200dc0246f0cb5405c80d18ac905c8350179c063ea1587580e3335bfc243ba6a"}, - {file = "aiohttp-3.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:158564d0d1020e0d3fe919a81d97aadad35171e13e7b425b244ad4337fc6793a"}, - {file = "aiohttp-3.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:da1346cd0ccb395f0ed16b113ebb626fa43b7b07fd7344fce33e7a4f04a8897a"}, - {file = "aiohttp-3.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:eaa9256de26ea0334ffa25f1913ae15a51e35c529a1ed9af8e6286dd44312554"}, - {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1543e7fb00214fb4ccead42e6a7d86f3bb7c34751ec7c605cca7388e525fd0b4"}, - {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:186e94570433a004e05f31f632726ae0f2c9dee4762a9ce915769ce9c0a23d89"}, - {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d52d20832ac1560f4510d68e7ba8befbc801a2b77df12bd0cd2bcf3b049e52a4"}, - {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c45e4e815ac6af3b72ca2bde9b608d2571737bb1e2d42299fc1ffdf60f6f9a1"}, - {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa906b9bdfd4a7972dd0628dbbd6413d2062df5b431194486a78f0d2ae87bd55"}, - {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:68bbee9e17d66f17bb0010aa15a22c6eb28583edcc8b3212e2b8e3f77f3ebe2a"}, - {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4c189b64bd6d9a403a1a3f86a3ab3acbc3dc41a68f73a268a4f683f89a4dec1f"}, - {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:8a7876f794523123bca6d44bfecd89c9fec9ec897a25f3dd202ee7fc5c6525b7"}, - {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:d23fba734e3dd7b1d679b9473129cd52e4ec0e65a4512b488981a56420e708db"}, - {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b141753be581fab842a25cb319f79536d19c2a51995d7d8b29ee290169868eab"}, - {file = "aiohttp-3.9.2-cp38-cp38-win32.whl", hash = "sha256:103daf41ff3b53ba6fa09ad410793e2e76c9d0269151812e5aba4b9dd674a7e8"}, - {file = "aiohttp-3.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:328918a6c2835861ff7afa8c6d2c70c35fdaf996205d5932351bdd952f33fa2f"}, - {file = "aiohttp-3.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5264d7327c9464786f74e4ec9342afbbb6ee70dfbb2ec9e3dfce7a54c8043aa3"}, - {file = "aiohttp-3.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07205ae0015e05c78b3288c1517afa000823a678a41594b3fdc870878d645305"}, - {file = "aiohttp-3.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ae0a1e638cffc3ec4d4784b8b4fd1cf28968febc4bd2718ffa25b99b96a741bd"}, - {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d43302a30ba1166325974858e6ef31727a23bdd12db40e725bec0f759abce505"}, - {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16a967685907003765855999af11a79b24e70b34dc710f77a38d21cd9fc4f5fe"}, - {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fa3ee92cd441d5c2d07ca88d7a9cef50f7ec975f0117cd0c62018022a184308"}, - {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b500c5ad9c07639d48615a770f49618130e61be36608fc9bc2d9bae31732b8f"}, - {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c07327b368745b1ce2393ae9e1aafed7073d9199e1dcba14e035cc646c7941bf"}, - {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cc7d6502c23a0ec109687bf31909b3fb7b196faf198f8cff68c81b49eb316ea9"}, - {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:07be2be7071723c3509ab5c08108d3a74f2181d4964e869f2504aaab68f8d3e8"}, - {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:122468f6fee5fcbe67cb07014a08c195b3d4c41ff71e7b5160a7bcc41d585a5f"}, - {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:00a9abcea793c81e7f8778ca195a1714a64f6d7436c4c0bb168ad2a212627000"}, - {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7a9825fdd64ecac5c670234d80bb52bdcaa4139d1f839165f548208b3779c6c6"}, - {file = "aiohttp-3.9.2-cp39-cp39-win32.whl", hash = "sha256:5422cd9a4a00f24c7244e1b15aa9b87935c85fb6a00c8ac9b2527b38627a9211"}, - {file = "aiohttp-3.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:7d579dcd5d82a86a46f725458418458fa43686f6a7b252f2966d359033ffc8ab"}, - {file = "aiohttp-3.9.2.tar.gz", hash = "sha256:b0ad0a5e86ce73f5368a164c10ada10504bf91869c05ab75d982c6048217fbf7"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82e6aa28dd46374f72093eda8bcd142f7771ee1eb9d1e223ff0fa7177a96b4a5"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f56455b0c2c7cc3b0c584815264461d07b177f903a04481dfc33e08a89f0c26b"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bca77a198bb6e69795ef2f09a5f4c12758487f83f33d63acde5f0d4919815768"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e083c285857b78ee21a96ba1eb1b5339733c3563f72980728ca2b08b53826ca5"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab40e6251c3873d86ea9b30a1ac6d7478c09277b32e14745d0d3c6e76e3c7e29"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df822ee7feaaeffb99c1a9e5e608800bd8eda6e5f18f5cfb0dc7eeb2eaa6bbec"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:acef0899fea7492145d2bbaaaec7b345c87753168589cc7faf0afec9afe9b747"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cd73265a9e5ea618014802ab01babf1940cecb90c9762d8b9e7d2cc1e1969ec6"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a78ed8a53a1221393d9637c01870248a6f4ea5b214a59a92a36f18151739452c"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6b0e029353361f1746bac2e4cc19b32f972ec03f0f943b390c4ab3371840aabf"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7cf5c9458e1e90e3c390c2639f1017a0379a99a94fdfad3a1fd966a2874bba52"}, + {file = "aiohttp-3.9.3-cp310-cp310-win32.whl", hash = "sha256:3e59c23c52765951b69ec45ddbbc9403a8761ee6f57253250c6e1536cacc758b"}, + {file = "aiohttp-3.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:055ce4f74b82551678291473f66dc9fb9048a50d8324278751926ff0ae7715e5"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4"}, + {file = "aiohttp-3.9.3-cp311-cp311-win32.whl", hash = "sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5"}, + {file = "aiohttp-3.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f"}, + {file = "aiohttp-3.9.3-cp312-cp312-win32.whl", hash = "sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38"}, + {file = "aiohttp-3.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ed621426d961df79aa3b963ac7af0d40392956ffa9be022024cd16297b30c8c"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7f46acd6a194287b7e41e87957bfe2ad1ad88318d447caf5b090012f2c5bb528"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feeb18a801aacb098220e2c3eea59a512362eb408d4afd0c242044c33ad6d542"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f734e38fd8666f53da904c52a23ce517f1b07722118d750405af7e4123933511"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b40670ec7e2156d8e57f70aec34a7216407848dfe6c693ef131ddf6e76feb672"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fdd215b7b7fd4a53994f238d0f46b7ba4ac4c0adb12452beee724ddd0743ae5d"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:017a21b0df49039c8f46ca0971b3a7fdc1f56741ab1240cb90ca408049766168"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99abf0bba688259a496f966211c49a514e65afa9b3073a1fcee08856e04425b"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:648056db9a9fa565d3fa851880f99f45e3f9a771dd3ff3bb0c048ea83fb28194"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8aacb477dc26797ee089721536a292a664846489c49d3ef9725f992449eda5a8"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:522a11c934ea660ff8953eda090dcd2154d367dec1ae3c540aff9f8a5c109ab4"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5bce0dc147ca85caa5d33debc4f4d65e8e8b5c97c7f9f660f215fa74fc49a321"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b4af9f25b49a7be47c0972139e59ec0e8285c371049df1a63b6ca81fdd216a2"}, + {file = "aiohttp-3.9.3-cp38-cp38-win32.whl", hash = "sha256:298abd678033b8571995650ccee753d9458dfa0377be4dba91e4491da3f2be63"}, + {file = "aiohttp-3.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:69361bfdca5468c0488d7017b9b1e5ce769d40b46a9f4a2eed26b78619e9396c"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0fa43c32d1643f518491d9d3a730f85f5bbaedcbd7fbcae27435bb8b7a061b29"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:835a55b7ca49468aaaac0b217092dfdff370e6c215c9224c52f30daaa735c1c1"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06a9b2c8837d9a94fae16c6223acc14b4dfdff216ab9b7202e07a9a09541168f"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abf151955990d23f84205286938796c55ff11bbfb4ccfada8c9c83ae6b3c89a3"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59c26c95975f26e662ca78fdf543d4eeaef70e533a672b4113dd888bd2423caa"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f95511dd5d0e05fd9728bac4096319f80615aaef4acbecb35a990afebe953b0e"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:595f105710293e76b9dc09f52e0dd896bd064a79346234b521f6b968ffdd8e58"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7c8b816c2b5af5c8a436df44ca08258fc1a13b449393a91484225fcb7545533"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f1088fa100bf46e7b398ffd9904f4808a0612e1d966b4aa43baa535d1b6341eb"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f59dfe57bb1ec82ac0698ebfcdb7bcd0e99c255bd637ff613760d5f33e7c81b3"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:361a1026c9dd4aba0109e4040e2aecf9884f5cfe1b1b1bd3d09419c205e2e53d"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:363afe77cfcbe3a36353d8ea133e904b108feea505aa4792dad6585a8192c55a"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e2c45c208c62e955e8256949eb225bd8b66a4c9b6865729a786f2aa79b72e9d"}, + {file = "aiohttp-3.9.3-cp39-cp39-win32.whl", hash = "sha256:f7217af2e14da0856e082e96ff637f14ae45c10a5714b63c77f26d8884cf1051"}, + {file = "aiohttp-3.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:27468897f628c627230dba07ec65dc8d0db566923c48f29e084ce382119802bc"}, + {file = "aiohttp-3.9.3.tar.gz", hash = "sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7"}, ] [package.dependencies] @@ -121,19 +121,6 @@ files = [ {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, ] -[[package]] -name = "algokit-client-generator" -version = "1.1.1" -description = "Algorand typed client Generator" -optional = false -python-versions = ">=3.10,<4.0" -files = [ - {file = "algokit_client_generator-1.1.1-py3-none-any.whl", hash = "sha256:56874d9f5dd6e8f013fd70fff4f58c04a54ee9448c5ee3cc00df9747f18506a6"}, -] - -[package.dependencies] -algokit-utils = ">=2.0.1,<3.0.0" - [[package]] name = "algokit-utils" version = "2.2.1" @@ -1341,16 +1328,6 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -2564,7 +2541,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -2572,16 +2548,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -2598,7 +2566,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -2606,7 +2573,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -3457,4 +3423,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "2400dbf00d7c90ebd77abba4a4b7445d3adfe245f82e140f6ebffd4c77028e0b" +content-hash = "f5be5f64853ae48ae3d34fea706abd698fa85369172a71be04e69580519184fc" diff --git a/pyproject.toml b/pyproject.toml index af95b3ff..cbc5dfa7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,6 @@ copier = "^9.0.0" questionary = "^1.10.0" pyclip = "^0.7.0" shellingham = "^1.5.0.post1" -algokit-client-generator = "^1.1.1" tomli = { version = "^2.0.1", python = "<3.11" } python-dotenv = "^1.0.0" mslex = "^1.1.0" @@ -24,7 +23,7 @@ auth0-python = "^4.4.0" algokit-utils = "^2.2.1" multiformats = "0.3.1" multiformats_config = "0.3.1" # pinned this to be in lockstep with multiformats -aiohttp = "3.9.2" +aiohttp = "3.9.3" jsondiff = "^2.0.0" [tool.poetry.group.dev.dependencies] diff --git a/src/algokit/cli/__init__.py b/src/algokit/cli/__init__.py index f7e95d74..18d59b2d 100644 --- a/src/algokit/cli/__init__.py +++ b/src/algokit/cli/__init__.py @@ -1,10 +1,8 @@ import click -from algokit.cli.bootstrap import bootstrap_group from algokit.cli.compile import compile_group from algokit.cli.completions import completions_group from algokit.cli.config import config_group -from algokit.cli.deploy import deploy_command from algokit.cli.dispenser import dispenser_group from algokit.cli.doctor import doctor_command from algokit.cli.explore import explore_command @@ -12,17 +10,36 @@ from algokit.cli.goal import goal_command from algokit.cli.init import init_command from algokit.cli.localnet import localnet_group +from algokit.cli.project import project_group +from algokit.cli.project.bootstrap import bootstrap_group +from algokit.cli.project.deploy import deploy_command from algokit.cli.task import task_group from algokit.core.conf import PACKAGE_NAME from algokit.core.log_handlers import color_option, verbose_option from algokit.core.version_prompt import do_version_prompt, skip_version_check_option +HIDDEN_COMMANDS: dict[str, click.Command] = {"deploy": deploy_command, "bootstrap": bootstrap_group} + + +class CustomGroup(click.Group): + def get_command(self, ctx: click.Context, cmd_name: str) -> click.Command | None: + rv = click.Group.get_command(self, ctx, cmd_name) + if rv is not None: + return rv + + # ensures hidden commands are still invocable yet not visible in help + if cmd_name in HIDDEN_COMMANDS: + return HIDDEN_COMMANDS[cmd_name] + + return None + @click.group( context_settings={ "help_option_names": ["-h", "--help"], "max_content_width": 120, }, + cls=CustomGroup, ) @click.version_option(package_name=PACKAGE_NAME) @verbose_option @@ -38,7 +55,6 @@ def algokit(*, skip_version_check: bool) -> None: do_version_prompt() -algokit.add_command(bootstrap_group) algokit.add_command(completions_group) algokit.add_command(config_group) algokit.add_command(doctor_command) @@ -47,7 +63,7 @@ def algokit(*, skip_version_check: bool) -> None: algokit.add_command(init_command) algokit.add_command(localnet_group) algokit.add_command(generate_group) -algokit.add_command(deploy_command) algokit.add_command(dispenser_group) algokit.add_command(task_group) algokit.add_command(compile_group) +algokit.add_command(project_group) diff --git a/src/algokit/cli/compile.py b/src/algokit/cli/compile.py index b5df7671..931de201 100644 --- a/src/algokit/cli/compile.py +++ b/src/algokit/cli/compile.py @@ -2,13 +2,18 @@ import click -from algokit.core.compile.python import find_valid_puyapy_command -from algokit.core.proc import run +from algokit.cli.compilers.python import py, python logger = logging.getLogger(__name__) -@click.group("compile", hidden=True) +@click.group( + "compile", + short_help=( + "Compile smart contracts and smart signatures written in a supported high-level language " + "to a format deployable on the Algorand Virtual Machine (AVM)." + ), +) @click.option( "-v", "--version", @@ -16,52 +21,22 @@ required=False, default=None, help=( - "Compiler version, for example, 1.0.0. " - "If the version isn't specified, AlgoKit will check if the compiler is installed locally, and execute that. " - "If the compiler is not found, it will install the latest version. " - "If the version is specified, AlgoKit will check if the local compiler's version satisfies, and execute that. " - "Otherwise, AlgoKit will install the specifed compiler version." + "The compiler version to pin to, for example, 1.0.0. " + "If no version is specified, AlgoKit checks if the compiler is installed and runs the installed version. " + "If the compiler is not installed, AlgoKit runs the latest version. " + "If a version is specified, AlgoKit checks if an installed version matches and runs the installed version. " + "Otherwise, AlgoKit runs the specified version." ), ) @click.pass_context def compile_group(context: click.Context, version: str | None) -> None: - """Compile high level language smart contracts to TEAL""" - context.ensure_object(dict) - context.obj["version"] = version - - -@click.command( - context_settings={ - "ignore_unknown_options": True, - }, - add_help_option=False, -) -@click.argument("puya_args", nargs=-1, type=click.UNPROCESSED) -@click.pass_context -def compile_py_command(context: click.Context, puya_args: list[str]) -> None: """ - Compile Python contract(s) to TEAL with PuyaPy + Compile smart contracts and smart signatures written in a supported high-level language + to a format deployable on the Algorand Virtual Machine (AVM). """ - version = str(context.obj["version"]) if context.obj["version"] else None - - puya_command = find_valid_puyapy_command(version) - - run_result = run( - [ - *puya_command, - *puya_args, - ], - ) - click.echo(run_result.output) - - if run_result.exit_code != 0: - click.secho( - "An error occurred during compile. Ensure supplied files are valid PuyaPy code before retrying.", - err=True, - fg="red", - ) - raise click.exceptions.Exit(run_result.exit_code) + context.ensure_object(dict) + context.obj["version"] = version -compile_group.add_command(compile_py_command, "python") -compile_group.add_command(compile_py_command, "py") +compile_group.add_command(python, "python") +compile_group.add_command(py, "py") diff --git a/tests/bootstrap/__init__.py b/src/algokit/cli/compilers/__init__.py similarity index 100% rename from tests/bootstrap/__init__.py rename to src/algokit/cli/compilers/__init__.py diff --git a/src/algokit/cli/compilers/python.py b/src/algokit/cli/compilers/python.py new file mode 100644 index 00000000..1a5f5f52 --- /dev/null +++ b/src/algokit/cli/compilers/python.py @@ -0,0 +1,58 @@ +import logging +import os +from collections.abc import Callable +from typing import Any + +import click + +from algokit.core.compilers.python import find_valid_puyapy_command +from algokit.core.proc import run + +logger = logging.getLogger(__name__) +_AnyCallable = Callable[..., Any] + + +def invoke_puyapy(context: click.Context, puyapy_args: list[str]) -> None: + version = str(context.obj["version"]) if context.obj["version"] else None + + puyapy_command = find_valid_puyapy_command(version) + + run_result = run( + [ + *puyapy_command, + *puyapy_args, + ], + env=(dict(os.environ) | {"NO_COLOR": "1"}) if context.color is False else None, + ) + click.echo(run_result.output) + + if run_result.exit_code != 0: + click.secho( + "An error occurred during compile. Please ensure that any supplied arguments are valid " + "and any files passed are valid Algorand Python code before retrying.", + err=True, + fg="red", + ) + raise click.exceptions.Exit(run_result.exit_code) + + +def common_puyapy_command_options(function: _AnyCallable) -> click.Command: + function = click.argument("puyapy_args", nargs=-1, type=click.UNPROCESSED)(function) + function = click.pass_context(function) + return click.command( + context_settings={ + "ignore_unknown_options": True, + }, + add_help_option=False, + help="Compile Algorand Python contract(s) using the PuyaPy compiler.", + )(function) + + +@common_puyapy_command_options +def python(context: click.Context, puyapy_args: list[str]) -> None: + invoke_puyapy(context, puyapy_args) + + +@common_puyapy_command_options +def py(context: click.Context, puyapy_args: list[str]) -> None: + invoke_puyapy(context, puyapy_args) diff --git a/src/algokit/cli/generate.py b/src/algokit/cli/generate.py index 5e5a517e..76346194 100644 --- a/src/algokit/cli/generate.py +++ b/src/algokit/cli/generate.py @@ -1,5 +1,6 @@ import logging import shutil +from functools import cache from pathlib import Path import click @@ -10,6 +11,7 @@ logger = logging.getLogger(__name__) +@cache def _load_custom_generate_commands(project_dir: Path) -> dict[str, click.Command]: """ Load custom generate commands from .algokit.toml file. @@ -125,17 +127,30 @@ def generate_group() -> None: type=click.Choice(ClientGenerator.languages()), help="Programming language of the generated client code", ) -def generate_client(output_path_pattern: str | None, app_spec_path_or_dir: Path, language: str | None) -> None: +@click.option( + "--version", + "-v", + "version", + default=None, + help="The client generator version to pin to, for example, 1.0.0. " + "If no version is specified, AlgoKit checks if the client generator is installed and runs the installed version. " + "If the client generator is not installed, AlgoKit runs the latest version. " + "If a version is specified, AlgoKit checks if an installed version matches and runs the installed version. " + "Otherwise, AlgoKit runs the specified version.", +) +def generate_client( + output_path_pattern: str | None, app_spec_path_or_dir: Path, language: str | None, version: str | None +) -> None: """Create a typed ApplicationClient from an ARC-32 application.json Supply the path to an application specification file or a directory to recursively search for "application.json" files""" if language is not None: - generator = ClientGenerator.create_for_language(language) + generator = ClientGenerator.create_for_language(language, version) elif output_path_pattern is not None: extension = Path(output_path_pattern).suffix try: - generator = ClientGenerator.create_for_extension(extension) + generator = ClientGenerator.create_for_extension(extension, version) except KeyError as ex: raise click.ClickException( "Could not determine language from file extension, Please use the --language option to specify a " diff --git a/src/algokit/cli/init.py b/src/algokit/cli/init.py index 261d66a9..f9fa6702 100644 --- a/src/algokit/cli/init.py +++ b/src/algokit/cli/init.py @@ -12,14 +12,20 @@ import questionary from algokit.core import proc, questionary_extensions -from algokit.core.bootstrap import ( +from algokit.core.conf import get_algokit_config +from algokit.core.init import ( + append_project_to_vscode_workspace, + get_git_user_info, + is_valid_project_dir_name, + resolve_vscode_workspace_file, +) +from algokit.core.log_handlers import EXTRA_EXCLUDE_FROM_CONSOLE +from algokit.core.project import ProjectType, get_workspace_project_path +from algokit.core.project.bootstrap import ( MAX_BOOTSTRAP_DEPTH, bootstrap_any_including_subdirs, project_minimum_algokit_version_check, ) -from algokit.core.conf import get_algokit_config -from algokit.core.init import ProjectType, get_git_user_info, is_valid_project_dir_name -from algokit.core.log_handlers import EXTRA_EXCLUDE_FROM_CONSOLE from algokit.core.sandbox import DEFAULT_ALGOD_PORT, DEFAULT_ALGOD_SERVER, DEFAULT_ALGOD_TOKEN, DEFAULT_INDEXER_PORT from algokit.core.utils import get_python_paths @@ -62,8 +68,6 @@ class TemplatePresetType(str, Enum): class ContractLanguage(Enum): """ For programming languages that have corresponding smart contract languages - python -> puya - typescript -> tealscript """ PYTHON = "Python 🐍" @@ -77,7 +81,7 @@ class TemplateKey(str, Enum): """ BASE = "base" - PUYA = "puya" + PYTHON = "python" TEALSCRIPT = "tealscript" FULLSTACK = "fullstack" REACT = "react" @@ -111,7 +115,7 @@ def __eq__(self, other: object) -> bool: LANGUAGE_TO_TEMPLATE_MAP = { - ContractLanguage.PYTHON: TemplateKey.PUYA, + ContractLanguage.PYTHON: TemplateKey.PYTHON, ContractLanguage.TYPESCRIPT: TemplateKey.TEALSCRIPT, ContractLanguage.PYTEAL: TemplateKey.BEAKER, } @@ -124,9 +128,9 @@ def _get_blessed_templates() -> dict[TemplateKey, BlessedTemplateSource]: url="gh:algorand-devrel/tealscript-algokit-template", description="Official starter template for TEALScript applications.", ), - TemplateKey.PUYA: BlessedTemplateSource( - url="gh:algorandfoundation/algokit-puya-template", - description="Official starter template for Puya applications (Dev Preview, not recommended for production)", + TemplateKey.PYTHON: BlessedTemplateSource( + url="gh:algorandfoundation/algokit-python-template", + description="Official starter template for Algorand Python applications", ), TemplateKey.REACT: BlessedTemplateSource( url="gh:algorandfoundation/algokit-react-frontend-template", @@ -158,7 +162,7 @@ def _get_blessed_templates() -> dict[TemplateKey, BlessedTemplateSource]: ) -def validate_dir_name(context: click.Context, param: click.Parameter, value: str | None) -> str | None: +def _validate_dir_name(context: click.Context, param: click.Parameter, value: str | None) -> str | None: if value is not None and not is_valid_project_dir_name(value): raise click.BadParameter( "Invalid directory name. Ensure it's a mix of letters, numbers, dashes, " @@ -169,6 +173,19 @@ def validate_dir_name(context: click.Context, param: click.Parameter, value: str return value +def _prevent_workspace_nesting(*, workspace_path: Path | None, project_path: Path, use_workspace: bool) -> None: + if not workspace_path: + return + + if use_workspace and workspace_path != project_path.parent: + logger.error( + "Error: Workspace nesting detected. Please run 'init' from the workspace root: " + f"'{workspace_path}'. For more info, refer to " + "https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/project/run.md" + ) + _fail_and_bail() + + @click.command("init", short_help="Initializes a new project from a template; run from project parent directory.") @click.option( "directory_name", @@ -176,7 +193,7 @@ def validate_dir_name(context: click.Context, param: click.Parameter, value: str "-n", type=str, help="Name of the project / directory / repository to create.", - callback=validate_dir_name, + callback=_validate_dir_name, ) @click.option( "template_name", @@ -236,7 +253,11 @@ def validate_dir_name(context: click.Context, param: click.Parameter, value: str "--workspace/--no-workspace", is_flag=True, default=True, - help="Whether to prefer structuring standalone projects as part of a workspace.", + help=( + "Whether to prefer structuring standalone projects as part of a workspace. " + "An AlgoKit workspace is a conventional project structure that allows managing " + "multiple standalone projects in a monorepo." + ), ) @click.option( "answers", @@ -248,7 +269,7 @@ def validate_dir_name(context: click.Context, param: click.Parameter, value: str default=[], metavar=" ", ) -def init_command( # noqa: PLR0913 +def init_command( # noqa: PLR0913, C901, PLR0915 *, directory_name: str | None, template_name: str | None, @@ -305,11 +326,17 @@ def init_command( # noqa: PLR0913 # allow skipping prompt if the template is the base template to avoid redundant # 're-using existing directory' warning in fullstack template init - root_project_path = _get_project_path( + project_path, overwrite_existing_dir = _get_project_path( directory_name_option=directory_name, force=template == _get_blessed_templates()[TemplateKey.BASE] ) - logger.debug(f"project path = {root_project_path}") - directory_name = root_project_path.name + workspace_path = get_workspace_project_path(project_path) + if not overwrite_existing_dir: + _prevent_workspace_nesting( + workspace_path=workspace_path, project_path=project_path, use_workspace=use_workspace + ) + + logger.debug(f"project path = {project_path}") + directory_name = project_path.name # provide the directory name as an answer to the template, if not explicitly overridden by user answers_dict.setdefault("project_name", directory_name) @@ -320,8 +347,9 @@ def init_command( # noqa: PLR0913 answers_dict.setdefault("python_path", "no_system_python_available") project_path = _resolve_workspace_project_path( - template_source=template, project_path=root_project_path, use_workspace=use_workspace + template_source=template, project_path=project_path, use_workspace=use_workspace ) + answers_dict.setdefault("use_workspace", "yes" if use_workspace else "no") logger.info(f"Starting template copy and render at {project_path}...") # copier is lazy imported for two reasons @@ -349,13 +377,12 @@ def init_command( # noqa: PLR0913 logger.info("Template render complete!") - _maybe_bootstrap(project_path, run_bootstrap=run_bootstrap, use_defaults=use_defaults, use_workspace=use_workspace) + # reload workspace path cause it might have been just introduced with new project instance + workspace_path = get_workspace_project_path(project_path) - _maybe_git_init( - root_project_path, - use_git=use_git, - commit_message=f"Project initialised with AlgoKit CLI using template: {expanded_template_url}", - ) + _maybe_move_github_folder(project_path=project_path, use_workspace=use_workspace) + + _maybe_bootstrap(project_path, run_bootstrap=run_bootstrap, use_defaults=use_defaults, use_workspace=use_workspace) logger.info( f"🙌 Project initialized at `{directory_name}`! For template specific next steps, " @@ -370,20 +397,31 @@ def init_command( # noqa: PLR0913 readme_path = next(project_path.glob("README*"), None) # Check if a .workspace file exists - workspace_file = next(project_path.glob("*.code-workspace"), None) + vscode_workspace_file = resolve_vscode_workspace_file(workspace_path or project_path) + + if vscode_workspace_file: + append_project_to_vscode_workspace(project_path=project_path, workspace_path=vscode_workspace_file) + + # Below must be ensured to run after all required filesystem changes are applied to ensure first commit captures + # all the changes introduced by init invocation + _maybe_git_init( + workspace_path or project_path, + use_git=use_git, + commit_message=f"Project initialised with AlgoKit CLI using template: {expanded_template_url}", + ) - if open_ide and (project_path / ".vscode").is_dir() and (code_cmd := shutil.which("code")): - target_path = str(project_path) + if ( + open_ide + and ((project_path / ".vscode").is_dir() or vscode_workspace_file) + and (code_cmd := shutil.which("code")) + ): + target_path = str(vscode_workspace_file if vscode_workspace_file else project_path) logger.info( "VSCode configuration detected in project directory, and 'code' command is available on path, " "attempting to launch VSCode" ) - if workspace_file: - logger.info(f"Detected VSCode workspace file. Opening workspace: {workspace_file}") - target_path = str(workspace_file) - code_cmd_and_args = [code_cmd, target_path] if readme_path: @@ -427,6 +465,52 @@ def _maybe_git_init(project_path: Path, *, use_git: bool | None, commit_message: _git_init(project_path, commit_message=commit_message) +def _maybe_move_github_folder(*, project_path: Path, use_workspace: bool) -> None: + """Move contents of .github folder from project_path to the root of the workspace if exists + and the workspace is used. + + Args: + project_path: The path to the project directory. + use_workspace: A flag to indicate if the project is initialized with workspace flag + """ + + source_dir = project_path / ".github" + + if ( + not use_workspace + or not source_dir.exists() + or not (workspace_root := get_workspace_project_path(project_path.parent)) + ): + return + + target_dir = workspace_root / ".github" + + for source_file in source_dir.rglob("*"): + if source_file.is_file(): + target_file = target_dir / source_file.relative_to(source_dir) + + if target_file.exists(): + logger.debug(f"Skipping move of {source_file.name} to {target_file} (duplicate exists)") + continue + + try: + target_file.parent.mkdir(parents=True, exist_ok=True) + shutil.move(str(source_file), str(target_file)) + except shutil.Error as e: + logger.debug(f"Skipping move of {source_file} to {target_file}: {e}") + + if any(p.is_file() for p in source_dir.rglob("*")): + click.secho( + "Failed to move all files within your project's .github folder to the workspace root. " + "Please review any files that remain in your project's .github folder and manually include " + "in the root .github directory as required.", + fg="yellow", + ) + else: + shutil.rmtree(source_dir) + logger.debug(f"No files found in .github folder after merge. Removing `.github` directory at {source_dir}...") + + def _fail_and_bail() -> NoReturn: logger.info("🛑 Bailing out... 👋") raise click.exceptions.Exit(code=1) @@ -463,7 +547,7 @@ def validate(self, document: prompt_toolkit.document.Document) -> None: ) -def _get_project_path(*, directory_name_option: str | None = None, force: bool = False) -> Path: +def _get_project_path(*, directory_name_option: str | None = None, force: bool = False) -> tuple[Path, bool]: """ Determines the project path based on the provided directory name option. @@ -473,15 +557,16 @@ def _get_project_path(*, directory_name_option: str | None = None, force: bool = force: A flag to auto accept warning prompts. Returns: - The path to the project directory. + The path to the project directory and a flag to indicate if the user agreed to overwrite the directory. """ base_path = Path.cwd() + overwrite_existing_dir = force directory_name = ( directory_name_option if directory_name_option is not None else questionary_extensions.prompt_text( - "Name of project / directory to create the project in: ", + "Name of project / directory to create the project in:", validators=[questionary_extensions.NonEmptyValidator(), DirectoryNameValidator(base_path)], ) ).strip() @@ -496,10 +581,11 @@ def _get_project_path(*, directory_name_option: str | None = None, force: bool = "Re-using existing directory, this is not recommended because if project " "generation fails, then we can't automatically cleanup." ) - if not questionary_extensions.prompt_confirm("Continue anyway?", default=False): + overwrite_existing_dir = questionary_extensions.prompt_confirm("Continue anyway?", default=False) + if not overwrite_existing_dir: return _get_project_path() if directory_name_option is None else _fail_and_bail() - return project_path + return project_path, overwrite_existing_dir def _get_template( @@ -571,7 +657,7 @@ def _get_template_interactive() -> TemplateSource: raise click.ClickException("No template selected. Please try again.") # Map the template string directly to the TemplateSource - # This is needed to be able to reuse fullstack to work with beaker, puya, and tealscript templates + # This is needed to be able to reuse fullstack to work with beaker, python, and tealscript templates blessed_templates = _get_blessed_templates() if template in blessed_templates: selected_template_source = blessed_templates[template] @@ -598,7 +684,7 @@ def _get_template_interactive() -> TemplateSource: " - ~/path/to/git/repo\n" " - ~/path/to/git/repo.bundle\n" ) - template_url = questionary_extensions.prompt_text("Custom template URL: ", validators=[GitRepoValidator()]).strip() + template_url = questionary_extensions.prompt_text("Custom template URL:", validators=[GitRepoValidator()]).strip() if not template_url: # re-prompt if empty response return _get_template_interactive() @@ -649,18 +735,20 @@ def _resolve_workspace_project_path( *, template_source: TemplateSource, project_path: Path, use_workspace: bool = True ) -> Path: blessed_template = _get_blessed_templates() + + # If its already a Base template, do not modify project path if template_source == blessed_template[TemplateKey.BASE]: return project_path cwd = Path.cwd() is_standalone = template_source != blessed_template[TemplateKey.FULLSTACK] - config = get_algokit_config(cwd) + config = get_algokit_config(project_dir=cwd) # 1. If standalone project (not fullstack) and use_workspace is True, bootstrap algokit-base-template if config is None and is_standalone and use_workspace: - _instantiate_base_template(project_path) + _init_base_template(target_path=project_path, is_blessed=template_source in blessed_template.values()) - config = get_algokit_config(project_path) + config = get_algokit_config(project_dir=project_path) if not config: logger.error("Failed to instantiate workspace structure for standalone project") _fail_and_bail() @@ -688,16 +776,25 @@ def _resolve_workspace_project_path( return project_path -def _instantiate_base_template(target_path: Path) -> None: +def _init_base_template(*, target_path: Path, is_blessed: bool) -> None: """ Instantiate the base template for a standalone project. Sets up the common workspace structure for standalone projects. + + Args: + target_path: The path to the project directory. + is_blessed: Whether the template is a blessed template. """ # Instantiate the base template blessed_templates = _get_blessed_templates() base_template = blessed_templates[TemplateKey.BASE] - base_template_answers = {"use_default_readme": "yes", "project_name": target_path.name} + base_template_answers = { + "use_default_readme": "yes", + "project_name": target_path.name, + "projects_root_path": "projects", + "include_github_workflow_template": not is_blessed, + } from copier.main import Worker with Worker( @@ -705,7 +802,7 @@ def _instantiate_base_template(target_path: Path) -> None: dst_path=target_path, data=base_template_answers, quiet=True, - vcs_ref=base_template.commit, + vcs_ref=base_template.branch or base_template.commit, unsafe=True, ) as copier_worker: copier_worker.run_copy() diff --git a/src/algokit/cli/project/__init__.py b/src/algokit/cli/project/__init__.py new file mode 100644 index 00000000..da493463 --- /dev/null +++ b/src/algokit/cli/project/__init__.py @@ -0,0 +1,23 @@ +import click + +from algokit.cli.project.bootstrap import bootstrap_group +from algokit.cli.project.deploy import deploy_command +from algokit.cli.project.link import link_command +from algokit.cli.project.list import list_command +from algokit.cli.project.run import run_group + + +@click.group( + "project", +) +def project_group() -> None: + """Provides a suite of commands for managing your AlgoKit project. + This includes initializing project dependencies, deploying smart contracts, + and executing predefined or custom commands within your project environment.""" + + +project_group.add_command(deploy_command) +project_group.add_command(bootstrap_group) +project_group.add_command(run_group) +project_group.add_command(list_command) +project_group.add_command(link_command) diff --git a/src/algokit/cli/bootstrap.py b/src/algokit/cli/project/bootstrap.py similarity index 63% rename from src/algokit/cli/bootstrap.py rename to src/algokit/cli/project/bootstrap.py index 1ccf45e0..8bcdbd5a 100644 --- a/src/algokit/cli/bootstrap.py +++ b/src/algokit/cli/project/bootstrap.py @@ -4,7 +4,8 @@ import click -from algokit.core.bootstrap import ( +from algokit.core.project import ProjectType +from algokit.core.project.bootstrap import ( bootstrap_any_including_subdirs, bootstrap_env, bootstrap_npm, @@ -21,11 +22,19 @@ @click.group( "bootstrap", short_help="Bootstrap local dependencies in an AlgoKit project; run from project root directory." ) -def bootstrap_group(*, force: bool) -> None: +@click.pass_context +def bootstrap_group(ctx: click.Context, *, force: bool) -> None: """ Expedited initial setup for any developer by installing and configuring dependencies and other key development environment setup activities. """ + + if ctx.parent and ctx.parent.command.name == "algokit": + click.secho( + "WARNING: The 'bootstrap' command group is scheduled for deprecation in v2.x release. " + "Please migrate to using 'algokit project bootstrap' instead.", + fg="yellow", + ) project_minimum_algokit_version_check(Path.cwd(), ignore_version_check_fail=force) @@ -38,9 +47,31 @@ def bootstrap_group(*, force: bool) -> None: default=lambda: "CI" not in os.environ, help="Enable/disable interactive prompts. If the CI environment variable is set, defaults to non-interactive", ) -def bootstrap_all(*, interactive: bool) -> None: +@click.option( + "project_names", + "--project-name", + "-p", + multiple=True, + help="(Optional) Projects to execute the command on. Defaults to all projects found in the current directory.", + nargs=1, + default=[], + metavar="", + required=False, +) +@click.option( + "project_type", + "--type", + "-t", + type=click.Choice([ProjectType.FRONTEND, ProjectType.CONTRACT, ProjectType.BACKEND]), + required=False, + default=None, + help="(Optional) Limit execution to specific project types if executing from workspace.", +) +def bootstrap_all(*, interactive: bool, project_names: tuple[str], project_type: str | None) -> None: cwd = Path.cwd() - bootstrap_any_including_subdirs(cwd, ci_mode=not interactive) + bootstrap_any_including_subdirs( + cwd, ci_mode=not interactive, project_names=list(project_names), project_type=project_type + ) logger.info(f"Finished bootstrapping {cwd}") diff --git a/src/algokit/cli/deploy.py b/src/algokit/cli/project/deploy.py similarity index 64% rename from src/algokit/cli/deploy.py rename to src/algokit/cli/project/deploy.py index 87912cbe..b46a3771 100644 --- a/src/algokit/cli/deploy.py +++ b/src/algokit/cli/project/deploy.py @@ -6,10 +6,13 @@ import click from algosdk.mnemonic import from_private_key +from algokit.cli.common.utils import MutuallyExclusiveOption from algokit.core import proc -from algokit.core.conf import ALGOKIT_CONFIG -from algokit.core.deploy import load_deploy_config, load_env_files, parse_command, resolve_command +from algokit.core.conf import ALGOKIT_CONFIG, get_algokit_config +from algokit.core.project import ProjectType, get_project_configs +from algokit.core.project.deploy import load_deploy_config, load_deploy_env_files from algokit.core.tasks.wallet import get_alias +from algokit.core.utils import resolve_command_path, split_command_string logger = logging.getLogger(__name__) @@ -77,6 +80,57 @@ def _ensure_environment_secrets( config_env[key] = click.prompt(key, hide_input=True) +def _execute_deploy_command( # noqa: PLR0913 + *, + path: Path, + environment_name: str | None, + command: list[str] | None, + interactive: bool, + deployer_alias: str | None, + dispenser_alias: str | None, +) -> None: + logger.debug(f"Deploying from project directory: {path}") + logger.debug("Loading deploy command from project config") + config = load_deploy_config(name=environment_name, project_dir=path) + if command: + config.command = command + elif not config.command: + if environment_name is None: + msg = f"No generic deploy command specified in '{ALGOKIT_CONFIG}' file." + else: + msg = ( + f"Deploy command for '{environment_name}' is not specified in '{ALGOKIT_CONFIG}' file, " + "and no generic command." + ) + raise click.ClickException(msg) + resolved_command = resolve_command_path(config.command) + logger.info(f"Using deploy command: {' '.join(resolved_command)}") + logger.info("Loading deployment environment variables...") + config_dotenv = load_deploy_env_files(environment_name, path) + # environment variables take precedence over those in .env* files + config_env = {**{k: v for k, v in config_dotenv.items() if v is not None}, **os.environ} + _ensure_aliases(config_env, deployer_alias=deployer_alias, dispenser_alias=dispenser_alias) + + if config.environment_secrets: + _ensure_environment_secrets( + config_env, + config.environment_secrets, + skip_mnemonics_prompts=not interactive, + ) + logger.info("Deploying smart contracts from AlgoKit compliant repository 🚀") + try: + result = proc.run(resolved_command, cwd=path, env=config_env, stdout_log_level=logging.INFO) + except FileNotFoundError as ex: + raise click.ClickException(f"Failed to execute deploy command, '{resolved_command[0]}' wasn't found") from ex + except PermissionError as ex: + raise click.ClickException( + f"Failed to execute deploy command '{resolved_command[0]}', permission denied" + ) from ex + else: + if result.exit_code != 0: + raise click.ClickException(f"Deployment command exited with error code = {result.exit_code}") + + class CommandParamType(click.types.StringParamType): name = "command" @@ -88,7 +142,7 @@ def convert( ) -> list[str]: str_value = super().convert(value=value, param=param, ctx=ctx) try: - return parse_command(str_value) + return split_command_string(str_value) except ValueError as ex: logger.debug(f"Failed to parse command string: {str_value}", exc_info=True) raise click.BadParameter(str(ex), param=param, ctx=ctx) from ex @@ -101,13 +155,17 @@ def convert( "-C", type=CommandParamType(), default=None, - help="Custom deploy command. If not provided, will load the deploy command from .algokit.toml file.", + help=("Custom deploy command. If not provided, will load the deploy command " "from .algokit.toml file."), + required=False, ) @click.option( "--interactive/--non-interactive", " /--ci", # this aliases --non-interactive to --ci default=lambda: "CI" not in os.environ, - help="Enable/disable interactive prompts. If the CI environment variable is set, defaults to non-interactive", + help=( + "Enable/disable interactive prompts. Defaults to non-interactive if the CI " + "environment variable is set. Interactive MainNet deployments prompt for confirmation." + ), ) @click.option( "--path", @@ -136,7 +194,24 @@ def convert( "if specified in .algokit.toml file." ), ) +@click.option( + "--project-name", + "-p", + "project_names", + multiple=True, + help="(Optional) Projects to execute the command on. Defaults to all projects found in the current directory.", + nargs=1, + default=[], + metavar="", + required=False, + cls=MutuallyExclusiveOption, + not_required_if=[ + "command", + ], +) +@click.pass_context def deploy_command( # noqa: PLR0913 + ctx: click.Context, *, environment_name: str | None, command: list[str] | None, @@ -144,46 +219,66 @@ def deploy_command( # noqa: PLR0913 path: Path, deployer_alias: str | None, dispenser_alias: str | None, + project_names: tuple[str], ) -> None: """Deploy smart contracts from AlgoKit compliant repository.""" - logger.debug(f"Deploying from project directory: {path}") - logger.debug("Loading deploy command from project config") - config = load_deploy_config(name=environment_name, project_dir=path) - if command: - config.command = command - elif not config.command: - if environment_name is None: - msg = f"No generic deploy command specified in '{ALGOKIT_CONFIG}' file." - else: - msg = ( - f"Deploy command for '{environment_name}' is not specified in '{ALGOKIT_CONFIG}' file, " - "and no generic command." - ) - raise click.ClickException(msg) - resolved_command = resolve_command(config.command) - logger.info(f"Using deploy command: {' '.join(resolved_command)}") - # TODO: [future-note] do we want to walk up for env/config? - logger.info("Loading deployment environment variables...") - config_dotenv = load_env_files(environment_name, path) - # environment variables take precedence over those in .env* files - config_env = {**{k: v for k, v in config_dotenv.items() if v is not None}, **os.environ} - _ensure_aliases(config_env, deployer_alias=deployer_alias, dispenser_alias=dispenser_alias) - if config.environment_secrets: - _ensure_environment_secrets( - config_env, - config.environment_secrets, - skip_mnemonics_prompts=not interactive, + if ctx.parent and ctx.parent.command.name == "algokit": + click.secho( + "WARNING: The 'deploy' command is scheduled for deprecation in v2.x release. " + "Please migrate to using 'algokit project deploy' instead.", + fg="yellow", ) - logger.info("Deploying smart contracts from AlgoKit compliant repository 🚀") - try: - result = proc.run(resolved_command, cwd=path, env=config_env, stdout_log_level=logging.INFO) - except FileNotFoundError as ex: - raise click.ClickException(f"Failed to execute deploy command, '{resolved_command[0]}' wasn't found") from ex - except PermissionError as ex: - raise click.ClickException( - f"Failed to execute deploy command '{resolved_command[0]}', permission denied" - ) from ex + + if interactive and environment_name and environment_name.lower() == "mainnet": + click.confirm( + click.style( + "Warning: Proceed with MainNet deployment?", + fg="yellow", + ), + default=True, + abort=True, + ) + + config = get_algokit_config() or {} + is_workspace = config.get("project", {}).get("type") == ProjectType.WORKSPACE + project_name = config.get("project", {}).get("name", None) + + if not is_workspace and project_names: + message = ( + f"Deploying `{project_name}`..." + if project_name in project_names + else "No project with the specified name found in the current directory or workspace." + ) + if project_name in project_names: + click.echo(message) + else: + raise click.ClickException(message) + + if is_workspace: + projects = get_project_configs(project_type=ProjectType.CONTRACT, project_names=project_names) + + for project in projects: + project_name = project.get("project", {}).get("name", None) + + if not project_name: + click.secho("WARNING: Skipping an unnamed project...", fg="yellow") + continue + + _execute_deploy_command( + path=project.get("cwd", None), + environment_name=environment_name, + command=None, + interactive=interactive, + deployer_alias=deployer_alias, + dispenser_alias=dispenser_alias, + ) else: - if result.exit_code != 0: - raise click.ClickException(f"Deployment command exited with error code = {result.exit_code}") + _execute_deploy_command( + path=path, + environment_name=environment_name, + command=command, + interactive=interactive, + deployer_alias=deployer_alias, + dispenser_alias=dispenser_alias, + ) diff --git a/src/algokit/cli/project/link.py b/src/algokit/cli/project/link.py new file mode 100644 index 00000000..7646e55d --- /dev/null +++ b/src/algokit/cli/project/link.py @@ -0,0 +1,249 @@ +import logging +import typing +from dataclasses import dataclass +from pathlib import Path + +import click +import questionary + +from algokit.cli.common.utils import MutuallyExclusiveOption +from algokit.core import questionary_extensions +from algokit.core.conf import get_algokit_config +from algokit.core.project import ProjectType, get_project_configs +from algokit.core.typed_client_generation import ClientGenerator + +logger = logging.getLogger(__name__) + + +@dataclass +class ContractArtifacts: + """Represents the contract project artifacts. + + Attributes: + project_name (str): The name of the project. + cwd (Path): The current working directory of the project. + """ + + project_name: str + cwd: Path + + +def _is_frontend(project_data: dict) -> bool: + """Determines if the project is a frontend project. + + Args: + project_data (dict): The project data to evaluate. + + Returns: + bool: True if the project is a frontend project, False otherwise. + """ + return project_data.get("type") == ProjectType.FRONTEND + + +def _get_contract_projects() -> list[ContractArtifacts]: + """Retrieves contract projects configurations. + + Returns: + list[ContractArtifacts]: A list of contract project artifacts. + """ + contract_configs = [] + try: + project_configs = get_project_configs(project_type="contract") + for config in project_configs: + project = config.get("project", {}) + project_type = project.get("type") + project_name = project.get("name") + project_cwd = config.get("cwd", Path.cwd()) + contract_artifacts = project.get("artifacts") + + if any([not project_type, not project_name, not project_cwd, not contract_artifacts]): + continue + + contract_configs.append(ContractArtifacts(project_name, project_cwd)) + + return contract_configs + except Exception: + return [] + + +def _link_projects( + *, + frontend_clients_path: Path, + contract_project_root: Path, + language: str, + fail_fast: bool, + version: str | None = None, +) -> None: + """Links projects by generating client code. + + Args: + frontend_clients_path (Path): The path to the frontend clients. + contract_project_root (Path): The root path of the contract project. + language (str): The programming language of the generated client code. + fail_fast (bool): Whether to exit immediately if a client generation process fails. + version (str | None): Version to pin the client generator to (Defaults to None). + """ + output_path_pattern = f"{frontend_clients_path}/{{contract_name}}.{'ts' if language == 'typescript' else 'py'}" + generator = ClientGenerator.create_for_language(language, version=version) + app_specs = list(contract_project_root.rglob("application.json")) + list( + contract_project_root.rglob("*.arc32.json") + ) + if not app_specs: + click.secho( + f"WARNING: No application.json | *.arc32.json files found in {contract_project_root}. Skipping...", + fg="yellow", + ) + return + + for app_spec in app_specs: + output_path = generator.resolve_output_path(app_spec, output_path_pattern) + if output_path is None: + if fail_fast: + raise click.ClickException(f"Error generating client for {app_spec}") + + logger.warning(f"Error generating client for {app_spec}") + continue + generator.generate(app_spec, output_path) + + +def _prompt_contract_project() -> ContractArtifacts | None: + """Prompts the user to select a contract project. + + Returns: + ContractArtifacts | None: The selected contract project artifacts or None if no projects are available. + """ + contract_projects = _get_contract_projects() + + if not contract_projects: + return None + + return typing.cast( + ContractArtifacts, + questionary_extensions.prompt_select( + "Select contract project to link with", + *[questionary.Choice(title=contract.project_name, value=contract) for contract in contract_projects], + ), + ) + + +def _select_contract_projects_to_link( + *, + project_names: typing.Sequence[str] | None = None, + link_all: bool = False, +) -> list[ContractArtifacts]: + """Selects contract projects to link based on criteria. + + Args: + project_names (typing.Sequence[str] | None): Specific project names to link. Defaults to None. + link_all (bool): Whether to link all projects. Defaults to False. + + Returns: + list[ContractArtifacts]: A list of contract project artifacts to link. + """ + if link_all: + return _get_contract_projects() + elif project_names: + return [project for project in _get_contract_projects() if project.project_name in project_names] + else: + contract_project = _prompt_contract_project() + return [contract_project] if contract_project else [] + + +@click.command("link") +@click.option( + "project_names", + "--project-name", + "-p", + multiple=True, + help="Specify contract projects for the command. Defaults to all in the current workspace.", + nargs=1, + default=[], + metavar="", + required=False, +) +@click.option( + "--language", + "-l", + default="typescript", + type=click.Choice(ClientGenerator.languages()), + help="Programming language of the generated client code", +) +@click.option( + "link_all", + "--all", + "-a", + help="Link all contract projects with the frontend project", + default=False, + is_flag=True, + type=click.BOOL, + required=False, + cls=MutuallyExclusiveOption, + not_required_if=["project_name"], +) +@click.option( + "fail_fast", + "--fail-fast", + "-f", + help="Exit immediately if at least one client generation process fails", + default=False, + is_flag=True, + type=click.BOOL, + required=False, +) +@click.option( + "--version", + "-v", + "version", + default=None, + help="The client generator version to pin to, for example, 1.0.0. " + "If no version is specified, AlgoKit checks if the client generator is installed and runs the installed version. " + "If the client generator is not installed, AlgoKit runs the latest version. " + "If a version is specified, AlgoKit checks if an installed version matches and runs the installed version. " + "Otherwise, AlgoKit runs the specified version.", +) +def link_command( + *, project_names: tuple[str] | None, language: str, link_all: bool, fail_fast: bool, version: str | None +) -> None: + """Automatically invoke 'algokit generate client' on contract projects available in the workspace. + Must be invoked from the root of a standalone 'frontend' typed project.""" + + config = get_algokit_config() or {} + project_data = config.get("project", {}) + + if not config: + click.secho("WARNING: No .algokit.toml config found. Skipping...", fg="yellow") + return + + if not _is_frontend(project_data): + click.secho("WARNING: This command is only available in projects of type `frontend`. Skipping...", fg="yellow") + return + + frontend_artifacts_path = project_data.get("artifacts") + if not frontend_artifacts_path: + raise click.ClickException("No `contract_clients` path specified in .algokit.toml") + + contract_projects = _select_contract_projects_to_link( + project_names=project_names, + link_all=link_all, + ) + + if not contract_projects: + click.secho( + f"WARNING: No {' '.join(project_names) if project_names else 'contract project(s)'} found. Skipping...", + fg="yellow", + ) + return + + iteration = 1 + total = len(contract_projects) + for contract_project in contract_projects: + _link_projects( + frontend_clients_path=Path.cwd() / frontend_artifacts_path, + contract_project_root=contract_project.cwd, + language=language, + fail_fast=fail_fast, + version=version, + ) + + logger.info(f"✅ {iteration}/{total}: Finished processing {contract_project.project_name}") + iteration += 1 diff --git a/src/algokit/cli/project/list.py b/src/algokit/cli/project/list.py new file mode 100644 index 00000000..c5ab1ddf --- /dev/null +++ b/src/algokit/cli/project/list.py @@ -0,0 +1,65 @@ +import logging +from pathlib import Path + +import click + +from algokit.core.conf import get_algokit_config +from algokit.core.project import ProjectType, get_project_configs, get_workspace_project_path + +logger = logging.getLogger(__name__) + + +PROJECT_TYPE_ICONS = { + ProjectType.CONTRACT: "📜", + ProjectType.FRONTEND: "đŸ–Ĩī¸", + ProjectType.WORKSPACE: "📁", + ProjectType.BACKEND: "⚙ī¸", +} + + +def _is_workspace(workspace_path: Path | None = None) -> bool: + config = get_algokit_config(project_dir=get_workspace_project_path(workspace_path)) or {} + project = config.get("project", {}) + return bool(project.get("type", None) == ProjectType.WORKSPACE) + + +@click.command("list") +@click.argument( + "workspace_path", + type=click.Path(exists=True, resolve_path=True, file_okay=False, dir_okay=True, readable=True, path_type=Path), + default=".", +) +def list_command(*, workspace_path: Path) -> None: + """List all projects in the workspace""" + + is_workspace = True + resolved_workspace_path = get_workspace_project_path(workspace_path) + if resolved_workspace_path is None: + is_workspace = False + + if not is_workspace: + click.secho( + "WARNING: No AlgoKit workspace found. Check [project.type] definition at .algokit.toml", + fg="yellow", + err=True, + ) + return + + configs = get_project_configs(resolved_workspace_path) + + if not configs: + click.secho( + "WARNING: No AlgoKit project(s) found in the workspace. Check [project.type] definition at .algokit.toml", + fg="yellow", + err=True, + ) + return + + click.echo(f"workspace: {resolved_workspace_path} {PROJECT_TYPE_ICONS[ProjectType.WORKSPACE]}") + for config in configs: + project = config.get("project", {}) + name, project_type = project.get("name"), project.get("type") + cwd = Path(config.get("cwd", Path.cwd())) + path_label = "this directory" if cwd == Path.cwd() else cwd + icon = PROJECT_TYPE_ICONS.get(project_type, "🔍 Unknown") + click.echo(f" - {name} ({path_label}) {icon}") diff --git a/src/algokit/cli/project/run.py b/src/algokit/cli/project/run.py new file mode 100644 index 00000000..2d2cc966 --- /dev/null +++ b/src/algokit/cli/project/run.py @@ -0,0 +1,183 @@ +import logging +from functools import cache +from pathlib import Path + +import click + +from algokit.cli.common.utils import MutuallyExclusiveOption +from algokit.core.project import ProjectType +from algokit.core.project.run import ( + ProjectCommand, + WorkspaceProjectCommand, + load_commands, + run_command, + run_workspace_command, +) + +logger = logging.getLogger(__name__) + + +@cache +def _load_project_commands(project_dir: Path) -> dict[str, click.Command]: + """ + Loads project commands from the .algokit.toml file located in the specified project directory. + + This function reads the project directory's .algokit.toml configuration file, extracts custom commands defined + within it, and returns a dictionary mapping command names to their corresponding Click command objects. + + Args: + project_dir (Path): The path to the project directory. + + Returns: + dict[str, click.Command]: A dictionary where keys are command names and values are Click command objects. + """ + + custom_commands = load_commands(project_dir) + + if custom_commands is None: + return {} + + commands_table: dict[str, click.Command] = {} + + for custom_command in custom_commands: + # Define the base command function + def base_command( + *, + args: list[str], + custom_command: ProjectCommand | WorkspaceProjectCommand = custom_command, + project_names: tuple[str] | None = None, + list_projects: bool = False, + project_type: str | None = None, + ) -> None: + """ + Executes a base command function with optional parameters for listing projects or specifying project names. + + This function serves as the base for executing both ProjectCommand and WorkspaceProjectCommand instances. + It handles listing projects within a workspace and executing commands for specific projects or all projects + within a workspace. + + Args: + args (list[str]): The command arguments to be passed to the custom command. + custom_command (ProjectCommand | WorkspaceProjectCommand): The custom command to be executed. + project_names (list[str] | None): Optional. A list of project names to execute the command on. + list_projects (bool): Optional. A flag indicating whether to list projects associated + with a workspace command. + project_type (str | None): Optional. Only execute commands in projects of specified type. + + Returns: + None + """ + if args: + logger.warning("Ignoring unrecognized arguments: %s.", " ".join(args)) + + if list_projects and isinstance(custom_command, WorkspaceProjectCommand): + for command in custom_command.commands: + cmds = " && ".join(" ".join(cmd) for cmd in command.commands) + logger.info(f"ℹī¸ Project: {command.project_name}, Command name: {command.name}, Command(s): {cmds}") # noqa: RUF001 + return + + run_command(command=custom_command) if isinstance( + custom_command, ProjectCommand + ) else run_workspace_command(custom_command, list(project_names or []), project_type) + + # Check if the command is a WorkspaceProjectCommand and conditionally decorate + is_workspace_command = isinstance(custom_command, WorkspaceProjectCommand) + command = click.argument("args", nargs=-1, type=click.UNPROCESSED, required=False)(base_command) + if is_workspace_command: + command = click.option( + "project_names", + "--project-name", + "-p", + multiple=True, + help=( + "Optional. Execute the command on specified projects. " + "Defaults to all projects in the current directory." + ), + nargs=1, + default=[], + required=False, + )(base_command) + command = click.option( + "list_projects", + "--list", + "-l", + help="(Optional) List all projects associated with workspace command", + default=False, + is_flag=True, + type=click.BOOL, + required=False, + cls=MutuallyExclusiveOption, + not_required_if=["project_names"], + )(command) + command = click.option( + "project_type", + "--type", + "-t", + type=click.Choice([ProjectType.FRONTEND, ProjectType.CONTRACT, ProjectType.BACKEND]), + required=False, + default=None, + help="Limit execution to specific project types if executing from workspace. (Optional)", + )(command) + + # Apply the click.command decorator with common options + command = click.command( + name=custom_command.name, + help=f"{custom_command.description}" or "Command description is not supplied.", + context_settings={ + # Enables workspace commands in standalone projects without execution impact, + # supporting uniform GitHub workflows across official templates. + "ignore_unknown_options": not is_workspace_command, + }, + )(command) + + commands_table[custom_command.name] = command + + return commands_table + + +class RunCommandGroup(click.Group): + """ + A custom Click command group for dynamically loading and executing project commands. + + This command group overrides the default Click command loading mechanism to include dynamically loaded project + commands from the .algokit.toml configuration file. It supports both predefined and dynamically loaded commands. + """ + + def get_command(self, ctx: click.Context, cmd_name: str) -> click.Command | None: + """ + Retrieves a command by name, including dynamically loaded project commands. + + Args: + ctx (click.Context): The current Click context. + cmd_name (str): The name of the command to retrieve. + + Returns: + click.Command | None: The requested command if found; otherwise, None. + """ + return_value = super().get_command(ctx, cmd_name) + + if return_value is not None: + return return_value + + return _load_project_commands(Path.cwd()).get(cmd_name) + + def list_commands(self, ctx: click.Context) -> list[str]: + """ + Lists all available commands, including dynamically loaded project commands. + + Args: + ctx (click.Context): The current Click context. + + Returns: + list[str]: A sorted list of all available command names. + """ + predefined_command_names = super().list_commands(ctx) + dynamic_commands = _load_project_commands(Path.cwd()) + dynamic_command_names = list(dynamic_commands) + + return sorted(predefined_command_names + dynamic_command_names) + + +@click.group("run", cls=RunCommandGroup) +def run_group() -> None: + """Define custom commands and manage their execution in you projects.""" diff --git a/src/algokit/core/compile/python.py b/src/algokit/core/compilers/python.py similarity index 50% rename from src/algokit/core/compile/python.py rename to src/algokit/core/compilers/python.py index e4e4b12c..de3ad787 100644 --- a/src/algokit/core/compile/python.py +++ b/src/algokit/core/compilers/python.py @@ -5,15 +5,15 @@ def find_valid_puyapy_command(version: str | None) -> list[str]: - return _find_puya_command_at_version(version) if version is not None else _find_puya_command() + return _find_puyapy_command_at_version(version) if version is not None else _find_puyapy_command() -def _find_puya_command_at_version(version: str) -> list[str]: +def _find_puyapy_command_at_version(version: str) -> list[str]: """ - Find puya command with a specific version. - If the puya version isn't installed, install it with pipx run. + Find puyapy command with a specific version. + If the puyapy version isn't installed, install it with pipx run. """ - for puyapy_command in _get_candidates_puyapy_commands(): + for puyapy_command in _get_candidate_puyapy_commands(): try: puyapy_version_result = run([*puyapy_command, "--version"]) except OSError: @@ -25,26 +25,27 @@ def _find_puya_command_at_version(version: str) -> list[str]: return puyapy_command pipx_command = find_valid_pipx_command( - "Unable to find pipx install so that the `PuyaPy` compiler can be installed; " + "Unable to find pipx install so that the `PuyaPy` compiler can be run; " "please install pipx via https://pypa.github.io/pipx/ " - "and then try `algokit compile py ...` again." + "and then try `algokit compile python ...` again." ) return [ *pipx_command, "run", - f"puya=={version}", + f"--spec=puyapy=={version}", + "puyapy", ] -def _find_puya_command() -> list[str]: +def _find_puyapy_command() -> list[str]: """ - Find puya command. - If puya isn't installed, install the latest version with pipx. + Find puyapy command. + If puyapy isn't installed, install the latest version with pipx. """ - for puyapy_command in _get_candidates_puyapy_commands(): + for puyapy_command in _get_candidate_puyapy_commands(): try: - puyapy_help_result = run([*puyapy_command, "-h"]) + puyapy_help_result = run([*puyapy_command, "--version"]) except OSError: pass # in case of path/permission issues, go to next candidate else: @@ -52,29 +53,20 @@ def _find_puya_command() -> list[str]: return puyapy_command pipx_command = find_valid_pipx_command( - "Unable to find pipx install so that the `PuyaPy` compiler can be installed; " + "Unable to find pipx install so that the `PuyaPy` compiler can be run; " "please install pipx via https://pypa.github.io/pipx/ " - "and then try `algokit compile py ...` again." - ) - _install_puyapy_with_pipx(pipx_command) - return ["puyapy"] - - -def _install_puyapy_with_pipx(pipx_command: list[str]) -> None: - run( - [ - *pipx_command, - "install", - "puya", - ], - bad_return_code_error_message=( - "Unable to install puya via pipx; please install puya manually and try `algokit compile py ...` again." - ), + "and then try `algokit compile python ...` again." ) + return [ + *pipx_command, + "run", + "--spec=puyapy", + "puyapy", + ] -def _get_candidates_puyapy_commands() -> Iterator[list[str]]: - # when puya is installed at the project level +def _get_candidate_puyapy_commands() -> Iterator[list[str]]: + # when puyapy is installed at the project level yield ["poetry", "run", "puyapy"] - # when puya is installed at the global level + # when puyapy is installed at the global level yield ["puyapy"] diff --git a/src/algokit/core/conf.py b/src/algokit/core/conf.py index 610744ec..22b3960e 100644 --- a/src/algokit/core/conf.py +++ b/src/algokit/core/conf.py @@ -55,10 +55,11 @@ def get_current_package_version() -> str: return metadata.version(PACKAGE_NAME) -def get_algokit_config(project_dir: Path | None = None) -> dict[str, t.Any] | None: +def get_algokit_config(*, project_dir: Path | None = None, verbose_validation: bool = False) -> dict[str, t.Any] | None: """ Load and parse a TOML configuration file. Will never throw. :param project_dir: Project directory path. + :param verbose_validation: Whether to warn user if toml validation failed. :return: A dictionary containing the configuration or None if not found. """ project_dir = project_dir or Path.cwd() @@ -72,29 +73,11 @@ def get_algokit_config(project_dir: Path | None = None) -> dict[str, t.Any] | No except Exception as ex: logger.debug(f"Unexpected error reading {ALGOKIT_CONFIG} file: {ex}", exc_info=True) return None - try: return tomllib.loads(config_text) except Exception as ex: - logger.debug(f"Error parsing {ALGOKIT_CONFIG} file: {ex}", exc_info=True) + if verbose_validation: + logger.warning(f"{ALGOKIT_CONFIG} file at {project_dir} is not valid toml! Skipping...", exc_info=True) + else: + logger.debug(f"Error parsing {ALGOKIT_CONFIG} file: {ex}", exc_info=True) return None - - -def get_algokit_projects_from_config(project_dir: Path | None = None) -> list[str]: - """ - Get the list of projects from the .algokit.toml file. - :return: List of projects. - """ - config = get_algokit_config(project_dir) - if config is None: - return [] - - project_root = config.get("project", {}).get("projects_root_path", None) - if project_root is None: - return [] - - project_root = Path(project_root) - if not project_root.exists(): - return [] - - return [p.name for p in Path(project_root).iterdir() if p.is_dir()] diff --git a/src/algokit/core/generate.py b/src/algokit/core/generate.py index a1838991..47d1e133 100644 --- a/src/algokit/core/generate.py +++ b/src/algokit/core/generate.py @@ -69,7 +69,7 @@ def load_generators(project_dir: Path) -> list[Generator]: :return: Generators. """ # Load and parse the TOML configuration file - config = get_algokit_config(project_dir) + config = get_algokit_config(project_dir=project_dir) generators: list[Generator] = [] if not config: diff --git a/src/algokit/core/init.py b/src/algokit/core/init.py index 9aed7dfb..b1cc3a46 100644 --- a/src/algokit/core/init.py +++ b/src/algokit/core/init.py @@ -1,11 +1,13 @@ +import json import re import shutil -from enum import Enum from logging import getLogger +from pathlib import Path +from typing import Any, cast from copier.main import MISSING, AnswersMap, Question, Worker # type: ignore[import] -from algokit.core.conf import get_algokit_projects_from_config +from algokit.core.project import get_project_dir_names_from_workspace logger = getLogger(__name__) @@ -13,16 +15,6 @@ DEFAULT_PROJECTS_ROOT_PATH = "projects" -class ProjectType(str, Enum): - """ - For distinguishing main template preset type question invoked by `algokit init` - """ - - WORKSPACE = "workspace" - BACKEND = "backend" # any project focused on smart contracts or standalone backend services - FRONTEND = "frontend" # any project focused on user facing services - - def populate_default_answers(worker: Worker) -> None: """Helper function to pre-populate Worker.data with default answers, based on Worker.answers implementation (see https://github.com/copier-org/copier/blob/v7.1.0/copier/main.py#L363). @@ -71,9 +63,59 @@ def get_git_user_info(param: str) -> str | None: def is_valid_project_dir_name(value: str) -> bool: """Check if the project directory name for algokit project is valid.""" - algokit_project_names = get_algokit_projects_from_config() + algokit_project_names = get_project_dir_names_from_workspace() if value in algokit_project_names: return False if not re.match(r"^[\w\-.]+$", value): return False return True + + +def resolve_vscode_workspace_file(project_root: Path | None) -> Path | None: + """Resolve the path to the VSCode workspace file for the given project. + Works by looking for algokit workspace and checking if there is a matching + vscode config at the same level.""" + if not project_root: + return None + return next(project_root.glob("*.code-workspace"), None) + + +def append_project_to_vscode_workspace(project_path: Path, workspace_path: Path) -> None: + """Append project to the code workspace, ensuring compatibility across Windows and Unix systems.""" + if not workspace_path.exists(): + raise FileNotFoundError(f"Workspace path {workspace_path} does not exist.") + + try: + workspace = _load_vscode_workspace(workspace_path) + # Convert paths to POSIX format for consistent handling, and ensure relative paths are correctly interpreted + processed_project_path = project_path.relative_to(workspace_path.parent).as_posix() + # Normalize the new project path for comparison, ensuring it does not end with a slash unless it's the root + normalized_project_path = processed_project_path if processed_project_path != "." else "./" + + # Normalize existing paths in the workspace for comparison + existing_paths = [ + folder.get("path", "").rstrip("/").replace("\\", "/") for folder in workspace.get("folders", []) + ] + # Ensure the normalized new path is not already in the workspace + if normalized_project_path not in existing_paths: + workspace.setdefault("folders", []).append({"path": processed_project_path}) + _save_vscode_workspace(workspace_path, workspace) + logger.debug(f"Appended project {project_path} to workspace {workspace_path}.") + except json.JSONDecodeError as json_err: + logger.warning(f"Invalid JSON format in the workspace file {workspace_path}. {json_err}") + except Exception as e: + logger.warning(f"Failed to append project {project_path} to workspace {workspace_path}. {e}") + + +def _load_vscode_workspace(workspace_path: Path) -> dict[str, Any]: + """Load the workspace file as a JSON object.""" + with workspace_path.open("r") as f: + data = json.load(f) + assert isinstance(data, dict) + return cast(dict[str, Any], data) + + +def _save_vscode_workspace(workspace_path: Path, workspace: dict) -> None: + """Save the modified workspace back to the file.""" + with workspace_path.open("w") as f: + json.dump(workspace, f, indent=2) diff --git a/src/algokit/core/project/__init__.py b/src/algokit/core/project/__init__.py new file mode 100644 index 00000000..08de5a2a --- /dev/null +++ b/src/algokit/core/project/__init__.py @@ -0,0 +1,170 @@ +from enum import Enum +from functools import cache +from pathlib import Path +from typing import Any + +from algokit.core.conf import ALGOKIT_CONFIG, get_algokit_config +from algokit.core.utils import alphanumeric_sort_key + +WORKSPACE_LOOKUP_LEVELS = 2 + + +class ProjectType(str, Enum): + """ + Enum class for specifying the type of algokit projects. + + Attributes: + WORKSPACE (str): Represents a workspace project type. + BACKEND (str): Represents a backend project type, typically for server-side operations. + FRONTEND (str): Represents a frontend project type, typically for client-side operations. + CONTRACT (str): Represents a contract project type, typically for blockchain contracts. + """ + + WORKSPACE = "workspace" + BACKEND = "backend" + FRONTEND = "frontend" + CONTRACT = "contract" + + +def _get_subprojects_paths(config: dict[str, Any], project_dir: Path) -> list[Path]: + """Searches for project directories within the specified workspace. It filters out directories that + do not contain an algokit configuration file. + + Args: + config (dict[str, Any]): The configuration of the project. + working directory is used. + project_dir (Path): The base directory to search for project root directories. If None, the current + working directory is used. + + Returns: + list[Path]: A list containing paths to project root directories that contain an algokit configuration file. + """ + + projects_root = config.get("project", {}).get("projects_root_path", None) + if projects_root is None: + return [] + + project_root_path = project_dir / projects_root + + if not project_root_path.exists(): + return [] + + return [ + sub_project + for sub_project in project_root_path.iterdir() + if sub_project.is_dir() and (sub_project / ALGOKIT_CONFIG).exists() + ] + + +@cache +def get_project_configs( + project_dir: Path | None = None, + lookup_level: int = WORKSPACE_LOOKUP_LEVELS, + project_type: str | None = None, + project_names: tuple[str, ...] | None = None, +) -> list[dict[str, Any]]: + """Recursively finds configurations for all algokit projects within the specified directory or the + current working directory. + + This function reads the .algokit.toml configuration file from each project directory and returns a list of + dictionaries, each representing a project's configuration. Additionally appends 'cwd' at the root of each dict + object loa + + Args: + project_dir (Path | None): The base directory to search for project configurations. If None, the current + working directory is used. + lookup_level (int): The number of levels to go up the directory to search for workspace projects + project_type (str | None): The type of project to filter by. If None, all project types are returned. + project_names (list[str] | None): The names of the projects to filter by. If None, all projects are returned. + + Returns: + list[dict[str, Any] | None]: A list of dictionaries, each containing the configuration of an algokit project. + Returns None for projects where the configuration could not be read. + """ + + if lookup_level < 0: + return [] + + project_dir = project_dir or Path.cwd() + project_config = get_algokit_config(project_dir=project_dir) + + if not project_config: + return get_project_configs( + project_dir=project_dir.parent, + lookup_level=lookup_level - 1, + project_type=project_type, + project_names=project_names, + ) + + configs = [] + for sub_project_dir in _get_subprojects_paths(project_config, project_dir): + config = get_algokit_config(project_dir=sub_project_dir) or {} + type_mismatch = project_type and config.get("project", {}).get("type") != project_type + name_mismatch = project_names and config.get("project", {}).get("name") not in project_names + if not type_mismatch and not name_mismatch: + config["cwd"] = sub_project_dir + configs.append(config) + + # Sort configs by the directory name alphanumerically + sorted_configs = sorted(configs, key=lambda x: alphanumeric_sort_key(x["cwd"].name)) + + return ( + sorted_configs + if sorted_configs + else get_project_configs( + project_dir=project_dir.parent, + lookup_level=lookup_level - 1, + project_type=project_type, + project_names=project_names, + ) + ) + + +@cache +def get_project_dir_names_from_workspace(project_dir: Path | None = None) -> list[str]: + """ + Generates a list of project names from the .algokit.toml file within the specified directory or the current + working directory. + + This function is useful for identifying all the projects within a given workspace by their names. + + Args: + project_dir (Path | None): The base directory to search for project names. If None, + the current working directory is used. + + Returns: + list[str]: A list of project names found within the specified directory. + """ + + project_dir = project_dir or Path.cwd() + config = get_algokit_config(project_dir=project_dir) + + if not config: + return [] + + return [p.name for p in _get_subprojects_paths(config, project_dir)] + + +def get_workspace_project_path( + project_dir: Path | None = None, lookup_level: int = WORKSPACE_LOOKUP_LEVELS +) -> Path | None: + """Recursively searches for the workspace project path within the specified directory. + + Args: + project_dir (Path): The base directory to search for the workspace project path. + lookup_level (int): The number of levels to go up the directory to search for workspace projects. + + Returns: + Path | None: The path to the workspace project directory or None if not found. + """ + + if lookup_level < 0: + return None + + project_dir = project_dir or Path.cwd() + project_config = get_algokit_config(project_dir=project_dir) + + if not project_config or project_config.get("project", {}).get("type") != ProjectType.WORKSPACE: + return get_workspace_project_path(project_dir=project_dir.parent, lookup_level=lookup_level - 1) + + return project_dir diff --git a/src/algokit/core/bootstrap.py b/src/algokit/core/project/bootstrap.py similarity index 90% rename from src/algokit/core/bootstrap.py rename to src/algokit/core/project/bootstrap.py index d7d66b61..d0d07744 100644 --- a/src/algokit/core/bootstrap.py +++ b/src/algokit/core/project/bootstrap.py @@ -34,17 +34,37 @@ def bootstrap_any(project_dir: Path, *, ci_mode: bool) -> None: bootstrap_npm(project_dir) -def bootstrap_any_including_subdirs( - base_path: Path, *, ci_mode: bool, max_depth: int = MAX_BOOTSTRAP_DEPTH, depth: int = 0 +def bootstrap_any_including_subdirs( # noqa: PLR0913 + base_path: Path, + *, + ci_mode: bool, + max_depth: int = MAX_BOOTSTRAP_DEPTH, + depth: int = 0, + project_names: list[str] | None = None, + project_type: str | None = None, ) -> None: if depth > max_depth: return - bootstrap_any(base_path, ci_mode=ci_mode) + config_project = (get_algokit_config(project_dir=base_path) or {}).get("project", {}) + skip = bool(config_project) and ( + (project_type and config_project.get("type") != project_type) + or (project_names and config_project.get("name") not in project_names) + ) + + if not skip: + bootstrap_any(base_path, ci_mode=ci_mode) for sub_dir in sorted(base_path.iterdir()): # sort needed for test output ordering if sub_dir.is_dir() and sub_dir.name.lower() not in [".venv", "node_modules", "__pycache__"]: - bootstrap_any_including_subdirs(sub_dir, ci_mode=ci_mode, max_depth=max_depth, depth=depth + 1) + bootstrap_any_including_subdirs( + sub_dir, + ci_mode=ci_mode, + max_depth=max_depth, + depth=depth + 1, + project_names=project_names, + project_type=project_type, + ) else: logger.debug(f"Skipping {sub_dir}") @@ -175,7 +195,7 @@ def bootstrap_npm(project_dir: Path) -> None: def get_min_algokit_version(project_dir: Path) -> str | None: - config = get_algokit_config(project_dir) + config = get_algokit_config(project_dir=project_dir) if config is None: return None try: diff --git a/src/algokit/core/deploy.py b/src/algokit/core/project/deploy.py similarity index 68% rename from src/algokit/core/deploy.py rename to src/algokit/core/project/deploy.py index 605de2c8..228eb25b 100644 --- a/src/algokit/core/deploy.py +++ b/src/algokit/core/project/deploy.py @@ -1,27 +1,23 @@ import dataclasses import logging -import platform -import shutil from pathlib import Path import click import dotenv from algokit.core.conf import ALGOKIT_CONFIG, get_algokit_config +from algokit.core.utils import load_env_file, split_command_string logger = logging.getLogger(__name__) -def load_env_files(name: str | None, project_dir: Path) -> dict[str, str | None]: +def load_deploy_env_files(name: str | None, project_dir: Path) -> dict[str, str | None]: """ Load the deploy configuration for the given network. :param name: Network name. :param project_dir: Project directory path. """ - general_env_path = project_dir / ".env" - result: dict[str, str | None] = {} - if general_env_path.exists(): - result = dotenv.dotenv_values(general_env_path, verbose=True) + result = load_env_file(project_dir) if name is not None: specific_env_path = project_dir / f".env.{name}" if not specific_env_path.exists(): @@ -45,7 +41,7 @@ def load_deploy_config(name: str | None, project_dir: Path) -> DeployConfig: """ # Load and parse the TOML configuration file - config = get_algokit_config(project_dir) + config = get_algokit_config(project_dir=project_dir) deploy_config = DeployConfig() @@ -53,9 +49,13 @@ def load_deploy_config(name: str | None, project_dir: Path) -> DeployConfig: # in the case of no algokit toml file, we return the (empty) defaults return deploy_config - # ensure there is at least some config under [deploy] and that it's a dict type - # (which should implicitly exist even if only [deploy.{name}] exists) - match deploy_table := config.get("deploy"): + # ensure there is at least some config under [project.deploy] and that it's a dict type + # (which should implicitly exist even if only [project.deploy.{name}] exists) + legacy_deploy_table = config.get("deploy") + project_deploy_table = config.get("project", {}).get("deploy", {}) + deploy_table = project_deploy_table or legacy_deploy_table + + match deploy_table: case dict(): pass # expected case if there is a file with deploy config case None: @@ -69,7 +69,7 @@ def load_deploy_config(name: str | None, project_dir: Path) -> DeployConfig: match tbl: case {"command": str(command)}: try: - deploy_config.command = parse_command(command) + deploy_config.command = split_command_string(command) except ValueError as ex: logger.debug(f"Failed to parse command string: {command}", exc_info=True) raise click.ClickException(f"Failed to parse command '{command}': {ex}") from ex @@ -84,25 +84,3 @@ def load_deploy_config(name: str | None, project_dir: Path) -> DeployConfig: raise click.ClickException(f"Invalid data provided under 'environment_secrets' key: {bad_data}") return deploy_config - - -def parse_command(command: str) -> list[str]: - if platform.system() == "Windows": - import mslex - - return mslex.split(command) - else: - import shlex - - return shlex.split(command) - - -def resolve_command(command: list[str]) -> list[str]: - cmd, *args = command - # if the command has any path separators or such, don't try and resolve - if Path(cmd).name != cmd: - return command - resolved_cmd = shutil.which(cmd) - if not resolved_cmd: - raise click.ClickException(f"Failed to resolve deploy command, '{cmd}' wasn't found") - return [resolved_cmd, *args] diff --git a/src/algokit/core/project/run.py b/src/algokit/core/project/run.py new file mode 100644 index 00000000..60afb0bf --- /dev/null +++ b/src/algokit/core/project/run.py @@ -0,0 +1,291 @@ +import dataclasses +import logging +import os +from concurrent.futures import ThreadPoolExecutor, as_completed +from pathlib import Path +from typing import Any + +import click + +from algokit.core.conf import ALGOKIT_CONFIG, get_algokit_config +from algokit.core.proc import run +from algokit.core.project import ProjectType +from algokit.core.utils import ( + load_env_file, + resolve_command_path, + split_command_string, +) + +logger = logging.getLogger("rich") + + +@dataclasses.dataclass(kw_only=True) +class ProjectCommand: + """Represents a command to be executed within a project context. + + Attributes: + name (str): The name of the command. + command (list[str]): The command to be executed, as a list of strings. + cwd (Path | None): The current working directory from which the command should be executed. + description (str | None): A brief description of the command. + project_name (str): The name of the project associated with this command. + """ + + name: str + project_type: str + commands: list[list[str]] + cwd: Path | None = None + description: str | None = None + project_name: str + env_file: Path | None + + +@dataclasses.dataclass(kw_only=True) +class WorkspaceProjectCommand: + """Represents a command that encompasses multiple project commands within a workspace. + + Attributes: + name (str): The name of the workspace command. + description (str | None): A brief description of the workspace command. + commands (list[ProjectCommand]): A list of `ProjectCommand` instances to be executed. + execution_order (list[str]): The order in which the commands should be executed. + """ + + name: str + description: str | None = None + commands: list[ProjectCommand] + execution_order: list[str] + + +def _load_commands_from_standalone( + config: dict[str, Any], + project_dir: Path, +) -> list[ProjectCommand]: + """Loads commands for standalone projects based on the project configuration. + + Args: + config (dict[str, Any]): The project configuration. + project_dir (Path): The directory of the project. + + Returns: + list[ProjectCommand]: A list of project commands derived from the configuration. + + Raises: + click.ClickException: If the project configuration is invalid. + """ + commands: list[ProjectCommand] = [] + project_config = config.get("project", {}) + project_commands = project_config.get("run", {}) + project_name = project_config.get("name") # Ensure name is present + project_type = project_config.get("type") + + if not project_name: + raise click.ClickException( + "Project name is required in the .algokit.toml file for projects of type 'contract', 'backend' or 'frontend" + ) + + if not isinstance(project_commands, dict): + raise click.ClickException(f"Bad data for [project.commands] key in '{ALGOKIT_CONFIG}'") + + for name, command_config in project_commands.items(): + raw_commands = command_config.get("commands") + description = command_config.get("description", "Description not available") + raw_env_file = command_config.get("env_file", None) + env_file = Path(raw_env_file) if raw_env_file else None + + if not raw_commands: + logger.debug(f"Command '{name}' has no custom commands to execute, skipping...") + continue + + commands.append( + ProjectCommand( + name=name, + commands=[split_command_string(cmd) for cmd in raw_commands], + cwd=project_dir, # Assumed to be Path object + description=description, + project_name=project_name, + env_file=env_file, + project_type=project_type, + ) + ) + + return commands + + +def _load_commands_from_workspace( + config: dict[str, Any], + project_dir: Path, +) -> list[WorkspaceProjectCommand]: + """Loads workspace commands based on the workspace configuration. + + Args: + config (dict[str, Any]): The workspace configuration. + project_dir (Path): The directory of the workspace. + + Returns: + list[WorkspaceProjectCommand]: A list of workspace project commands derived from the configuration. + """ + workspace_commands: dict[str, WorkspaceProjectCommand] = {} + execution_order = config.get("project", {}).get("run", {}) + sub_projects_root = config.get("project", {}).get("projects_root_path") + + if not sub_projects_root: + logger.warning("Missing 'projects_root_path' in workspace config; skipping command loading") + return [] + + sub_projects_root_dir = project_dir / sub_projects_root + if not sub_projects_root_dir.exists() or not sub_projects_root_dir.is_dir(): + logger.warning(f"Path {sub_projects_root_dir} does not exist or is not a directory, skipping...") + return [] + + for subproject_dir in sorted(sub_projects_root_dir.iterdir(), key=lambda p: p.name): + if not subproject_dir.is_dir(): + continue + + subproject_config = get_algokit_config(project_dir=subproject_dir, verbose_validation=True) + if not subproject_config: + continue + + standalone_commands = _load_commands_from_standalone(subproject_config, subproject_dir) + + for standalone_cmd in standalone_commands: + if standalone_cmd.name not in workspace_commands: + workspace_commands[standalone_cmd.name] = WorkspaceProjectCommand( + name=standalone_cmd.name, + description=f'Run all "{standalone_cmd.name}" commands in the workspace project.', + commands=[standalone_cmd], + execution_order=execution_order.get(standalone_cmd.name, []), + ) + else: + workspace_commands[standalone_cmd.name].commands.append(standalone_cmd) + + return list(workspace_commands.values()) + + +def run_command(*, command: ProjectCommand, from_workspace: bool = False) -> None: + """Executes a specified project command. + + Args: + command (ProjectCommand): The project command to be executed. + from_workspace (bool): Indicates whether the command is being executed from a workspace context. + + Raises: + click.ClickException: If the command execution fails. + """ + is_verbose = not from_workspace or logger.level == logging.DEBUG + + if is_verbose: + logger.info(f"Running `{command.name}` command in {command.cwd}...") + + config_dotenv = ( + load_env_file(command.env_file) if command.env_file else load_env_file(command.cwd) if command.cwd else {} + ) + # environment variables take precedence over those in .env* files + config_env = {**{k: v for k, v in config_dotenv.items() if v is not None}, **os.environ} + + for index, cmd in enumerate(command.commands): + try: + resolved_command = resolve_command_path(cmd) + except click.ClickException as e: + logger.error(f"'{command.name}' failed executing: '{' '.join(cmd)}'") + raise e + + result = run( + command=resolved_command, + cwd=command.cwd, + env=config_env, + stdout_log_level=logging.DEBUG, + ) + + if result.exit_code != 0: + header = f" project run '{command.name}' command output: ".center(80, "¡") + logger.error(f"\n{header}\n{result.output}") + raise click.ClickException( + f"'{command.name}' failed executing '{' '.join(cmd)}' with exit code = {result.exit_code}" + ) + + # Log after each command if not from workspace, and also log success after the last command + if is_verbose: + log_msg = f"Command Executed: '{' '.join(cmd)}'\nOutput: {result.output}\n" + if index == len(command.commands) - 1: + log_msg += f"✅ {command.project_name}: '{' '.join(cmd)}' executed successfully." + logger.info(log_msg) + + +def run_workspace_command( + workspace_command: WorkspaceProjectCommand, + project_names: list[str] | None = None, + project_type: str | None = None, +) -> None: + """Executes a workspace command, potentially limited to specified projects. + + Args: + workspace_command (WorkspaceProjectCommand): The workspace command to be executed. + project_names (list[str] | None): Optional; specifies a subset of projects to execute the command for. + project_type (str | None): Optional; specifies a subset of project types to execute the command for. + """ + + def _execute_command(cmd: ProjectCommand) -> None: + """Helper function to execute a single project command within the workspace context.""" + logger.info(f"âŗ {cmd.project_name}: '{cmd.name}' command in progress...") + try: + run_command(command=cmd, from_workspace=True) + executed_commands = " && ".join(" ".join(command) for command in cmd.commands) + logger.info(f"✅ {cmd.project_name}: '{executed_commands}' executed successfully.") + except Exception as e: + logger.error(f"❌ {cmd.project_name}: {e}") + raise click.ClickException(f"failed to execute '{cmd.name}' command in '{cmd.project_name}'") from e + + if workspace_command.execution_order: + logger.info("Detected execution order, running commands sequentially") + order_map = {name: i for i, name in enumerate(workspace_command.execution_order)} + sorted_commands = sorted( + workspace_command.commands, key=lambda c: order_map.get(c.project_name, len(order_map)) + ) + + if project_names: + existing_projects = {cmd.project_name for cmd in workspace_command.commands} + missing_projects = set(project_names) - existing_projects + if missing_projects: + logger.warning(f"Missing projects: {', '.join(missing_projects)}. Proceeding with available ones.") + + for cmd in sorted_commands: + if ( + project_names + and cmd.project_name not in project_names + or (project_type and project_type != cmd.project_type) + ): + continue + _execute_command(cmd) + else: + with ThreadPoolExecutor() as executor: + futures = { + executor.submit(_execute_command, cmd): cmd + for cmd in workspace_command.commands + if (not project_names or cmd.project_name in project_names) + and (not project_type or project_type == cmd.project_type) + } + for future in as_completed(futures): + future.result() + + +def load_commands(project_dir: Path) -> list[ProjectCommand] | list[WorkspaceProjectCommand] | None: + """Determines and loads the appropriate project commands based on the project type. + + Args: + project_dir (Path): The directory of the project. + + Returns: + list[ProjectCommand] | list[WorkspaceProjectCommand] | None: A list of project or workspace commands, + or None if the project configuration is not found. + """ + config = get_algokit_config(project_dir=project_dir, verbose_validation=True) + if not config: + return None + + project_type = config.get("project", {}).get("type") + return ( + _load_commands_from_workspace(config, project_dir) + if project_type == ProjectType.WORKSPACE + else _load_commands_from_standalone(config, project_dir) + ) diff --git a/src/algokit/core/typed_client_generation.py b/src/algokit/core/typed_client_generation.py index 82caa30b..048898dd 100644 --- a/src/algokit/core/typed_client_generation.py +++ b/src/algokit/core/typed_client_generation.py @@ -1,21 +1,22 @@ import abc import json import logging -import os import re -import shutil +import shutil # noqa: F401 from pathlib import Path from typing import ClassVar -import algokit_client_generator import click from algokit.core import proc -from algokit.core.utils import is_windows +from algokit.core.utils import extract_version_triple, find_valid_pipx_command, get_npm_command logger = logging.getLogger(__name__) -TYPESCRIPT_NPX_PACKAGE = "@algorandfoundation/algokit-client-generator@^2.5.0" +TYPESCRIPT_NPM_PACKAGE = "@algorandfoundation/algokit-client-generator" +TYPESCRIPT_GENERATE_COMMAND = "algokitgen-ts" +PYTHON_PYPI_PACKAGE = "algokit-client-generator" +PYTHON_GENERATE_COMMAND = "algokitgen-py" def _snake_case(s: str) -> str: @@ -28,10 +29,14 @@ def _snake_case(s: str) -> str: class ClientGenerator(abc.ABC): language: ClassVar[str] extension: ClassVar[str] + version: str | None _by_language: ClassVar[dict[str, type["ClientGenerator"]]] = {} _by_extension: ClassVar[dict[str, type["ClientGenerator"]]] = {} + def __init__(self, version: str | None) -> None: + self.command = self.find_generate_command(version) + def __init_subclass__(cls, language: str, extension: str) -> None: cls.language = language cls.extension = extension @@ -43,12 +48,12 @@ def languages(cls) -> list[str]: return list(cls._by_language.keys()) @classmethod - def create_for_language(cls, language: str) -> "ClientGenerator": - return cls._by_language[language]() + def create_for_language(cls, language: str, version: str | None) -> "ClientGenerator": + return cls._by_language[language](version) @classmethod - def create_for_extension(cls, extension: str) -> "ClientGenerator": - return cls._by_extension[extension]() + def create_for_extension(cls, extension: str, version: str | None) -> "ClientGenerator": + return cls._by_extension[extension](version) def resolve_output_path(self, app_spec: Path, output_path_pattern: str | None) -> Path | None: try: @@ -76,6 +81,10 @@ def generate(self, app_spec: Path, output: Path) -> None: def default_output_pattern(self) -> str: ... + @abc.abstractmethod + def find_generate_command(self, version: str | None) -> list[str]: + ... + def format_contract_name(self, contract_name: str) -> str: return contract_name @@ -83,7 +92,23 @@ def format_contract_name(self, contract_name: str) -> str: class PythonClientGenerator(ClientGenerator, language="python", extension=".py"): def generate(self, app_spec: Path, output: Path) -> None: logger.info(f"Generating Python client code for application specified in {app_spec} and writing to {output}") - algokit_client_generator.generate_client(app_spec, output) + cmd = [ + *self.command, + "-a", + str(app_spec), + "-o", + str(output), + ] + run_result = proc.run(cmd) + click.echo(run_result.output) + + if run_result.exit_code != 0: + click.secho( + f"Client generation failed for {app_spec}.", + err=True, + fg="red", + ) + raise click.exceptions.Exit(run_result.exit_code) @property def default_output_pattern(self) -> str: @@ -92,51 +117,171 @@ def default_output_pattern(self) -> str: def format_contract_name(self, contract_name: str) -> str: return _snake_case(contract_name) + def find_project_generate_command(self, version: str | None) -> list[str] | None: + """ + Try find the generate command in the project. + """ + try: + # Use the tree output as it puts the package info on the first line of the output + result = proc.run(["poetry", "show", PYTHON_PYPI_PACKAGE, "--tree"]) + if result.exit_code == 0: + generate_command = ["poetry", "run", PYTHON_GENERATE_COMMAND] + if version is not None: + installed_version = None + lines = result.output.splitlines() + if len(lines) > 0: + installed_version = extract_version_triple(lines[0]) + if extract_version_triple(version) == installed_version: + return generate_command + else: + return generate_command + except OSError: + pass + except ValueError: + pass -class TypeScriptClientGenerator(ClientGenerator, language="typescript", extension=".ts"): - def __init__(self) -> None: - npx_path = shutil.which("npx") - if not npx_path: - raise click.ClickException("Typescript generator requires Node.js and npx to be installed.") - - # Create the npm directory inside %APPDATA% if it doesn't exist, as npx on windows needs this. - # See https://github.com/npm/cli/issues/7089 for more details. - if is_windows(): - appdata_dir = os.getenv("APPDATA") - if appdata_dir is not None: - appdata_dir_path = Path(appdata_dir).expanduser() - npm_dir = appdata_dir_path / "npm" - try: - if not npm_dir.exists(): - npm_dir.mkdir(parents=True) - except OSError as ex: - logger.debug(ex) - raise click.ClickException( - f"Failed to create the `npm` directory in {appdata_dir_path}.\n" - "This command uses `npx`, which requires the `npm` directory to exist " - "in the above path, otherwise an ENOENT 4058 error will occur.\n" - "Please create this directory manually and try again." - ) from ex + return None - def generate(self, app_spec: Path, output: Path) -> None: - cmd = [ - "npx" if not is_windows() else "npx.cmd", - "--yes", - TYPESCRIPT_NPX_PACKAGE, - "generate", - "-a", - str(app_spec), - "-o", - str(output), + def find_global_generate_command(self, pipx_command: list[str], version: str | None) -> list[str] | None: + """ + Try find the generate command installed globally. + """ + try: + result = proc.run([*pipx_command, "list", "--short"]) + if result.exit_code == 0: + generate_command = [PYTHON_GENERATE_COMMAND] + for line in result.output.splitlines(): + if PYTHON_PYPI_PACKAGE in line: + if version is not None: + installed_version = None + installed_version = extract_version_triple(line) + if extract_version_triple(version) == installed_version: + return generate_command + else: + return generate_command + except OSError: + pass + except ValueError: + pass + + return None + + def find_generate_command(self, version: str | None) -> list[str]: + """ + Find Python generator command. + If a matching version is installed at a project level, use that. + If a matching version is installed at a global level, use that. + Otherwise, run the matching version via pipx. + """ + + logger.debug("Searching for project installed client generator") + project_result = self.find_project_generate_command(version) + if project_result is not None: + return project_result + + pipx_command = find_valid_pipx_command( + f"Unable to find pipx install so that the `{PYTHON_PYPI_PACKAGE}` can be run; " + "please install pipx via https://pypa.github.io/pipx/ " + "and then try `algokit generate client ...` again." + ) + + logger.debug("Searching for globally installed client generator") + global_result = self.find_global_generate_command(pipx_command, version) + if global_result is not None: + return global_result + + # when not installed, run via pipx + logger.debug("No matching installed client generator found, run client generator via pipx") + return [ + *pipx_command, + "run", + f"--spec={PYTHON_PYPI_PACKAGE}{f'=={version}' if version is not None else ''}", + PYTHON_GENERATE_COMMAND, ] + + +class TypeScriptClientGenerator(ClientGenerator, language="typescript", extension=".ts"): + def generate(self, app_spec: Path, output: Path) -> None: + cmd = [*self.command, "generate", "-a", str(app_spec), "-o", str(output)] logger.info( f"Generating TypeScript client code for application specified in {app_spec} and writing to {output}" ) - proc.run( - cmd, - bad_return_code_error_message=f"Client generation failed for {app_spec}.", + run_result = proc.run(cmd) + click.echo(run_result.output) + + if run_result.exit_code != 0: + click.secho( + f"Client generation failed for {app_spec}.", + err=True, + fg="red", + ) + raise click.exceptions.Exit(run_result.exit_code) + + def find_project_generate_command( + self, npm_command: list[str], npx_command: list[str], version: str | None + ) -> list[str] | None: + try: + result = proc.run([*npm_command, "ls"]) + if result.exit_code == 0: + generate_command = [*npx_command, TYPESCRIPT_NPM_PACKAGE] + for line in result.output.splitlines(): + if TYPESCRIPT_NPM_PACKAGE in line: + if version is not None: + installed_version = extract_version_triple(line) + if extract_version_triple(version) == installed_version: + return generate_command + else: + return generate_command + except OSError: + pass + except ValueError: + pass + + return None + + def find_global_generate_command( + self, npm_command: list[str], npx_command: list[str], version: str | None + ) -> list[str] | None: + return self.find_project_generate_command([*npm_command, "--global"], npx_command, version) + + def find_generate_command(self, version: str | None) -> list[str]: + """ + Find TypeScript generator command. + If a matching version is installed at a project level, use that. + If a matching version is installed at a global level, use that. + Otherwise, run the matching version via npx. + """ + + npm_command = get_npm_command( + f"Unable to find npm install so that the `{TYPESCRIPT_NPM_PACKAGE}` can be run; " + "please install npm via https://docs.npmjs.com/downloading-and-installing-node-js-and-npm " + "and then try `algokit generate client ...` again.", + ) + npx_command = get_npm_command( + f"Unable to find npx install so that the `{TYPESCRIPT_NPM_PACKAGE}` can be run; " + "please install npx via https://www.npmjs.com/package/npx " + "and then try `algokit generate client ...` again.", + is_npx=True, ) + logger.debug("Searching for project installed client generator") + project_result = self.find_project_generate_command(npm_command, npx_command, version) + if project_result is not None: + return project_result + + logger.debug("Searching for globally installed client generator") + global_result = self.find_global_generate_command(npm_command, npx_command, version) + if global_result is not None: + return global_result + + # when not installed, run via npx + logger.debug("No matching installed client generator found, run client generator via npx") + return [ + *npx_command, + "--yes", + f"{TYPESCRIPT_NPM_PACKAGE}@{version if version is not None else 'latest'}", + ] + @property def default_output_pattern(self) -> str: return f"{{contract_name}}Client{self.extension}" diff --git a/src/algokit/core/utils.py b/src/algokit/core/utils.py index e2afb2d2..41cb78e1 100644 --- a/src/algokit/core/utils.py +++ b/src/algokit/core/utils.py @@ -1,23 +1,30 @@ import codecs +import os import platform import re +import shutil import socket import sys import threading import time from collections.abc import Callable, Iterator from concurrent.futures import ThreadPoolExecutor +from os import environ from pathlib import Path from shutil import which from typing import Any import click +import dotenv from algokit.core import proc CLEAR_LINE = "\033[K" SPINNER_FRAMES = ["⠋", "⠙", "â š", "â ¸", "â ŧ", "â ´", "â Ļ", "â §", "⠇", "⠏"] +# From _WIN_DEFAULT_PATHEXT from shutils +WIN_DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC" + def extract_version_triple(version_str: str) -> str: match = re.search(r"\d+\.\d+\.\d+", version_str) @@ -60,12 +67,14 @@ def animate(name: str, stop_event: threading.Event) -> None: except Exception: text = frame output = f"\r{text} {name}" - sys.stdout.write(output) - sys.stdout.write(CLEAR_LINE) - sys.stdout.flush() + sys.stdout.buffer.write(output.encode("utf-8")) + sys.stdout.buffer.write(CLEAR_LINE.encode("utf-8")) + sys.stdout.buffer.flush() time.sleep(0.001 * spinner["interval"]) # type: ignore # noqa: PGH003 - sys.stdout.write("\r ") + sys.stdout.buffer.write(b"\r") + sys.stdout.buffer.write(b"\n") + sys.stdout.buffer.flush() def run_with_animation( @@ -112,6 +121,32 @@ def get_candidate_pipx_commands() -> Iterator[list[str]]: yield [python_path, "-m", "pipx"] +def get_npm_command(error_message: str, *, is_npx: bool = False) -> list[str]: + command = "npx" if is_npx else "npm" + path = shutil.which(command) + if not path: + raise click.ClickException(error_message) + # Create the npm directory inside %APPDATA% if it doesn't exist, as npx on windows needs this. + # See https://github.com/npm/cli/issues/7089 for more details. + if is_windows(): + appdata_dir = os.getenv("APPDATA") + if appdata_dir is not None: + appdata_dir_path = Path(appdata_dir).expanduser() + npm_dir = appdata_dir_path / "npm" + try: + if not npm_dir.exists(): + npm_dir.mkdir(parents=True) + except OSError as ex: + raise click.ClickException( + f"Failed to create the `npm` directory in {appdata_dir_path}.\n" + "This command uses `npx`, which requires the `npm` directory to exist " + "in the above path, otherwise an ENOENT 4058 error will occur.\n" + "Please create this directory manually and try again." + ) from ex + return [f"{command}.cmd"] + return [command] + + def get_python_paths() -> Iterator[str]: for python_name in ("python3", "python"): if python_path := which(python_name): @@ -161,3 +196,83 @@ def is_binary_mode() -> bool: def is_windows() -> bool: return platform.system() == "Windows" + + +def split_command_string(command: str) -> list[str]: + """ + Parses a command string into a list of arguments, handling both shell and non-shell commands + """ + + if platform.system() == "Windows": + import mslex + + return mslex.split(command) + else: + import shlex + + return shlex.split(command) + + +def resolve_command_path( + command: list[str], +) -> list[str]: + """ + Encapsulates custom command resolution, promotes reusability + + Args: + command (list[str]): The command to resolve + allow_chained_commands (bool): Whether to allow chained commands (e.g. "&&" or "||") + + Returns: + list[str]: The resolved command + """ + + cmd, *args = command + + # No resolution needed if the command already has a path or is not Windows-specific + if Path(cmd).name != cmd: + return command + + # Windows-specific handling if 'shutil.which' fails: + if is_windows(): + for ext in environ.get("PATHEXT", WIN_DEFAULT_PATHEXT).split(";"): + potential_path = shutil.which(cmd + ext) + if potential_path: + return [potential_path, *args] + + # If resolves directly, return + if resolved_cmd := shutil.which(cmd): + return [resolved_cmd, *args] + + # Command not found with any extension + raise click.ClickException(f"Failed to resolve command path, '{cmd}' wasn't found") + + +def load_env_file(path: Path) -> dict[str, str | None]: + """Load the general .env configuration. + + Args: + path (Path): Path to the .env file or directory containing the .env file. + + Returns: + dict[str, str | None]: Dictionary with .env configurations. + """ + + # Check if the path is a file, if yes, use it directly + if path.is_file(): + env_path = path + else: + # Assume the default .env file name in the given directory + env_path = path / ".env" + + if env_path.exists(): + return dotenv.dotenv_values(env_path, verbose=True) + return {} + + +def alphanumeric_sort_key(s: str) -> list[int | str]: + """ + Generate a key for sorting strings that contain both text and numbers. + For instance, ensures that "name_digit_1" comes before "name_digit_2". + """ + return [int(text) if text.isdigit() else text.lower() for text in re.split("([0-9]+)", s)] diff --git a/tests/compile/conftest.py b/tests/compile/conftest.py index 44dfde6b..34a8e11e 100644 --- a/tests/compile/conftest.py +++ b/tests/compile/conftest.py @@ -1,5 +1,5 @@ -VALID_PUYA_CONTRACT_FILE_CONTENT = """ -from puyapy import Contract, Txn, log +VALID_ALGORAND_PYTHON_CONTRACT_FILE_CONTENT = """ +from algopy import Contract, Txn, log class HelloWorldContract(Contract): @@ -12,8 +12,8 @@ def clear_state_program(self) -> bool: return True """ -INVALID_PUYA_CONTRACT_FILE_CONTENT = """ -from puyapy import Contract, Txn, log +INVALID_ALGORAND_PYTHON_CONTRACT_FILE_CONTENT = """ +from algopy import Contract, Txn, log class HelloWorldContract(Contract): diff --git a/tests/compile/test_python.py b/tests/compile/test_python.py index 2139ae16..808ce9e0 100644 --- a/tests/compile/test_python.py +++ b/tests/compile/test_python.py @@ -1,15 +1,23 @@ +import logging import os +import subprocess import sys from pathlib import Path import pytest +from algokit.core.utils import is_windows from pytest_mock import MockerFixture -from tests.compile.conftest import INVALID_PUYA_CONTRACT_FILE_CONTENT, VALID_PUYA_CONTRACT_FILE_CONTENT +from tests.compile.conftest import ( + INVALID_ALGORAND_PYTHON_CONTRACT_FILE_CONTENT, + VALID_ALGORAND_PYTHON_CONTRACT_FILE_CONTENT, +) from tests.utils.approvals import verify from tests.utils.click_invoker import invoke from tests.utils.proc_mock import ProcMock +logger = logging.getLogger(__name__) + def _normalize_path(path: Path) -> str: return str(path.absolute()).replace("\\", r"\\") @@ -32,6 +40,7 @@ def output_path(cwd: Path) -> Path: def test_compile_py_help(mocker: MockerFixture) -> None: proc_mock = ProcMock() + proc_mock.set_output(["poetry", "run", "puyapy", "--version"], output=["puyapy 1.0.0"]) proc_mock.set_output(["poetry", "run", "puyapy", "-h"], output=["Puyapy help"]) mocker.patch("algokit.core.proc.Popen").side_effect = proc_mock.popen @@ -43,8 +52,8 @@ def test_compile_py_help(mocker: MockerFixture) -> None: def test_puyapy_is_not_installed_anywhere(dummy_contract_path: Path, mocker: MockerFixture) -> None: proc_mock = ProcMock() - proc_mock.should_bad_exit_on(["poetry", "run", "puyapy", "-h"], exit_code=1, output=["Puyapy not found"]) - proc_mock.should_bad_exit_on(["puyapy", "-h"], exit_code=1, output=["Puyapy not found"]) + proc_mock.should_bad_exit_on(["poetry", "run", "puyapy", "--version"], exit_code=1, output=["Puyapy not found"]) + proc_mock.should_bad_exit_on(["puyapy", "--version"], exit_code=1, output=["Puyapy not found"]) proc_mock.set_output(["pipx", "--version"], ["1.0.0"]) @@ -64,7 +73,7 @@ def test_specificed_puyapy_version_is_not_installed(dummy_contract_path: Path, m target_version = "1.1.0" proc_mock = ProcMock() - proc_mock.set_output(["poetry", "run", "puyapy", "--version"], output=[current_version]) + proc_mock.set_output(["poetry", "run", "puyapy", "--version"], output=[f"puyapy {current_version}"]) proc_mock.should_bad_exit_on(["puyapy", "--version"], exit_code=1, output=["Puyapy not found"]) proc_mock.set_output(["pipx", "--version"], ["1.0.0"]) @@ -80,7 +89,7 @@ def test_specificed_puyapy_version_is_not_installed(dummy_contract_path: Path, m def test_puyapy_is_installed_in_project(dummy_contract_path: Path, mocker: MockerFixture) -> None: proc_mock = ProcMock() - proc_mock.set_output(["poetry", "run", "puyapy", "-h"], output=["Puyapy help"]) + proc_mock.set_output(["poetry", "run", "puyapy", "--version"], output=["puyapy 1.0.0"]) proc_mock.set_output(["poetry", "run", "puyapy", str(dummy_contract_path)], ["Done"]) mocker.patch("algokit.core.proc.Popen").side_effect = proc_mock.popen @@ -93,9 +102,10 @@ def test_puyapy_is_installed_in_project(dummy_contract_path: Path, mocker: Mocke def test_puyapy_is_installed_globally(dummy_contract_path: Path, mocker: MockerFixture) -> None: proc_mock = ProcMock() - proc_mock.should_bad_exit_on(["poetry", "run", "puyapy", "-h"], exit_code=1, output=["Puyapy not found"]) - proc_mock.set_output(["puyapy", "-h"], output=["Puyapy help"]) + proc_mock.should_bad_exit_on(["poetry", "run", "puyapy", "--version"], exit_code=1, output=["Puyapy not found"]) + + proc_mock.set_output(["puyapy", "--version"], output=["puyapy 1.0.0"]) proc_mock.set_output(["puyapy", str(dummy_contract_path)], ["Done"]) mocker.patch("algokit.core.proc.Popen").side_effect = proc_mock.popen @@ -106,15 +116,28 @@ def test_puyapy_is_installed_globally(dummy_contract_path: Path, mocker: MockerF verify(result.output) -@pytest.mark.skipif(sys.version_info < (3, 12), reason="PuyaPy requires python3.12 or higher") +# TODO: NC - Temporarily disable this test on windows +@pytest.mark.skipif(sys.version_info < (3, 12) or is_windows(), reason="PuyaPy requires python3.12 or higher") def test_valid_contract(cwd: Path, output_path: Path) -> None: - # Set NO_COLOR to 1 to avoid requirements for colorama on Windows - os.environ["NO_COLOR"] = "1" + subprocess.run([sys.executable, "-m", "venv", ".venv"], check=True, cwd=cwd) + venv_path = cwd / ".venv" + + puyapy_env = { + # Set NO_COLOR to 1 to avoid requirements for colorama on Windows + "NO_COLOR": "1", + "VIRTUAL_ENV": str(venv_path), + "PYTHONHOME": "", + "PATH": (f"{venv_path / 'Scripts'};" if is_windows() else f"{venv_path / 'bin'}:" f"{os.environ['PATH']}"), + } + + subprocess.run(["pip", "install", "algorand-python"], check=True, cwd=cwd, env=dict(os.environ) | puyapy_env) contract_path = cwd / "contract.py" - contract_path.write_text(VALID_PUYA_CONTRACT_FILE_CONTENT) + contract_path.write_text(VALID_ALGORAND_PYTHON_CONTRACT_FILE_CONTENT) - result = invoke(f"compile python {_normalize_path(contract_path)} --out-dir {_normalize_path(output_path)}") + result = invoke( + f"compile python {_normalize_path(contract_path)} --out-dir {_normalize_path(output_path)}", env=puyapy_env + ) # Only check for the exit code, don't check the results from PuyaPy assert result.exit_code == 0 @@ -126,7 +149,7 @@ def test_invalid_contract(cwd: Path, output_path: Path) -> None: os.environ["NO_COLOR"] = "1" contract_path = cwd / "contract.py" - contract_path.write_text(INVALID_PUYA_CONTRACT_FILE_CONTENT) + contract_path.write_text(INVALID_ALGORAND_PYTHON_CONTRACT_FILE_CONTENT) result = invoke(f"compile python {_normalize_path(contract_path)} --out-dir {_normalize_path(output_path)}") # Only check for the exit code and the error message from AlgoKit CLI diff --git a/tests/compile/test_python.test_compile_py_help.approved.txt b/tests/compile/test_python.test_compile_py_help.approved.txt index 17b77cdc..4239a0ef 100644 --- a/tests/compile/test_python.test_compile_py_help.approved.txt +++ b/tests/compile/test_python.test_compile_py_help.approved.txt @@ -1,5 +1,5 @@ -DEBUG: Running 'poetry run puyapy -h' in '{current_working_directory}' -DEBUG: poetry: Puyapy help +DEBUG: Running 'poetry run puyapy --version' in '{current_working_directory}' +DEBUG: poetry: puyapy 1.0.0 DEBUG: Running 'poetry run puyapy -h' in '{current_working_directory}' DEBUG: poetry: Puyapy help Puyapy help diff --git a/tests/compile/test_python.test_puyapy_is_installed_globally.approved.txt b/tests/compile/test_python.test_puyapy_is_installed_globally.approved.txt index ba256b52..e08133ac 100644 --- a/tests/compile/test_python.test_puyapy_is_installed_globally.approved.txt +++ b/tests/compile/test_python.test_puyapy_is_installed_globally.approved.txt @@ -1,7 +1,7 @@ -DEBUG: Running 'poetry run puyapy -h' in '{current_working_directory}' +DEBUG: Running 'poetry run puyapy --version' in '{current_working_directory}' DEBUG: poetry: Puyapy not found -DEBUG: Running 'puyapy -h' in '{current_working_directory}' -DEBUG: puyapy: Puyapy help +DEBUG: Running 'puyapy --version' in '{current_working_directory}' +DEBUG: puyapy: puyapy 1.0.0 DEBUG: Running 'puyapy {current_working_directory}/tests/compile/dummy_contract.py' in '{current_working_directory}' DEBUG: puyapy: Done Done diff --git a/tests/compile/test_python.test_puyapy_is_installed_in_project.approved.txt b/tests/compile/test_python.test_puyapy_is_installed_in_project.approved.txt index c2963d12..ec7a6523 100644 --- a/tests/compile/test_python.test_puyapy_is_installed_in_project.approved.txt +++ b/tests/compile/test_python.test_puyapy_is_installed_in_project.approved.txt @@ -1,5 +1,5 @@ -DEBUG: Running 'poetry run puyapy -h' in '{current_working_directory}' -DEBUG: poetry: Puyapy help +DEBUG: Running 'poetry run puyapy --version' in '{current_working_directory}' +DEBUG: poetry: puyapy 1.0.0 DEBUG: Running 'poetry run puyapy {current_working_directory}/tests/compile/dummy_contract.py' in '{current_working_directory}' DEBUG: poetry: Done Done diff --git a/tests/compile/test_python.test_puyapy_is_not_installed_anywhere.approved.txt b/tests/compile/test_python.test_puyapy_is_not_installed_anywhere.approved.txt index 9eb04315..d512daa2 100644 --- a/tests/compile/test_python.test_puyapy_is_not_installed_anywhere.approved.txt +++ b/tests/compile/test_python.test_puyapy_is_not_installed_anywhere.approved.txt @@ -1,11 +1,11 @@ -DEBUG: Running 'poetry run puyapy -h' in '{current_working_directory}' +DEBUG: Running 'poetry run puyapy --version' in '{current_working_directory}' DEBUG: poetry: Puyapy not found -DEBUG: Running 'puyapy -h' in '{current_working_directory}' +DEBUG: Running 'puyapy --version' in '{current_working_directory}' DEBUG: puyapy: Puyapy not found DEBUG: Running 'pipx --version' in '{current_working_directory}' DEBUG: pipx: 1.0.0 -DEBUG: Running 'pipx install puya' in '{current_working_directory}' -DEBUG: pipx: Puyapy is installed -DEBUG: Running 'puyapy {current_working_directory}/tests/compile/dummy_contract.py' in '{current_working_directory}' -DEBUG: puyapy: Done -Done +DEBUG: Running 'pipx run --spec=puyapy puyapy {current_working_directory}/tests/compile/dummy_contract.py' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +STDOUT +STDERR diff --git a/tests/compile/test_python.test_specificed_puyapy_version_is_not_installed.approved.txt b/tests/compile/test_python.test_specificed_puyapy_version_is_not_installed.approved.txt index 0cf292cd..83e05df4 100644 --- a/tests/compile/test_python.test_specificed_puyapy_version_is_not_installed.approved.txt +++ b/tests/compile/test_python.test_specificed_puyapy_version_is_not_installed.approved.txt @@ -1,9 +1,11 @@ DEBUG: Running 'poetry run puyapy --version' in '{current_working_directory}' -DEBUG: poetry: 1.0.0 +DEBUG: poetry: puyapy 1.0.0 DEBUG: Running 'puyapy --version' in '{current_working_directory}' DEBUG: puyapy: Puyapy not found DEBUG: Running 'pipx --version' in '{current_working_directory}' DEBUG: pipx: 1.0.0 -DEBUG: Running 'pipx run puya==1.1.0 {current_working_directory}/tests/compile/dummy_contract.py' in '{current_working_directory}' -DEBUG: pipx: Done -Done +DEBUG: Running 'pipx run --spec=puyapy==1.1.0 puyapy {current_working_directory}/tests/compile/dummy_contract.py' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +STDOUT +STDERR diff --git a/tests/conftest.py b/tests/conftest.py index 635d9665..8cf5e87e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,6 +10,7 @@ import pytest import questionary from algokit.core import questionary_extensions +from algokit.core.project import get_project_configs, get_project_dir_names_from_workspace from approvaltests import Reporter, reporters, set_default_reporter from approvaltests.reporters.generic_diff_reporter_config import create_config from approvaltests.reporters.generic_diff_reporter_factory import GenericDiffReporter @@ -159,6 +160,8 @@ def log_prompt_confirm(message: str, *, default: bool) -> None: else [] ) default_reporters += [ + # reporters.ReporterThatAutomaticallyApproves(), # noqa: ERA001 + # # uncomment to auto approve all received files, do not commit to VCS! GenericDiffReporter(create_config(["kdiff3", "/usr/bin/kdiff3"])), GenericDiffReporter(create_config(["DiffMerge", "/Applications/DiffMerge.app/Contents/MacOS/DiffMerge"])), GenericDiffReporter(create_config(["TortoiseGit", "{ProgramFiles}\\TortoiseGit\\bin\\TortoiseGitMerge.exe"])), @@ -219,3 +222,9 @@ def dummy_algokit_template_with_python_task(tmp_path_factory: pytest.TempPathFac subprocess.run(["git", "add", "."], cwd=dummy_template_path, check=False) subprocess.run(["git", "commit", "-m", "chore: setup dummy test template"], cwd=dummy_template_path, check=False) return {"template_path": dummy_template_path, "cwd": cwd} + + +@pytest.fixture(autouse=True) +def _clear_caches() -> None: + get_project_dir_names_from_workspace.cache_clear() + get_project_configs.cache_clear() diff --git a/tests/generate/test_generate_client.py b/tests/generate/test_generate_client.py index 7c4f61d2..3d409a90 100644 --- a/tests/generate/test_generate_client.py +++ b/tests/generate/test_generate_client.py @@ -4,7 +4,11 @@ import pytest from _pytest.tmpdir import TempPathFactory -from algokit.core.typed_client_generation import TYPESCRIPT_NPX_PACKAGE, _snake_case +from algokit.core.typed_client_generation import ( + PYTHON_PYPI_PACKAGE, + TYPESCRIPT_NPM_PACKAGE, + _snake_case, +) from algokit.core.utils import is_windows from approvaltests.namer import NamerFactory from approvaltests.pytest.py_test_namer import PyTestNamer @@ -19,13 +23,31 @@ def _normalize_output(output: str) -> str: - return output.replace("\\", "/").replace(TYPESCRIPT_NPX_PACKAGE, "{typed_client_package}") + return output.replace("\\", "/") def _get_npx_command() -> str: return "npx" if not is_windows() else "npx.cmd" +def _get_npm_command() -> str: + return "npm" if not is_windows() else "npm.cmd" + + +def _get_python_generate_command(version: str | None, application_json: Path, expected_output_path: Path) -> str: + return ( + f"pipx run --spec={PYTHON_PYPI_PACKAGE}{f'=={version}' if version is not None else ''} " + f"algokitgen-py -a {application_json} -o {expected_output_path}" + ) + + +def _get_typescript_generate_command(version: str | None, application_json: Path, expected_output_path: Path) -> str: + return ( + f"{_get_npx_command()} --yes {TYPESCRIPT_NPM_PACKAGE}{f'@{version}' if version is not None else 'latest'} " + f"generate -a {application_json} -o {expected_output_path}" + ) + + @pytest.fixture() def cwd(tmp_path_factory: TempPathFactory) -> Path: return tmp_path_factory.mktemp("cwd") @@ -58,6 +80,8 @@ def arc32_json(cwd: Path, dir_with_app_spec_factory: DirWithAppSpecFactory) -> P def which_mock(mocker: MockerFixture) -> WhichMock: which_mock = WhichMock() which_mock.add("npx") + which_mock.add("npm") + which_mock.add("pipx") mocker.patch("algokit.core.typed_client_generation.shutil.which").side_effect = which_mock.which return which_mock @@ -82,15 +106,89 @@ def test_generate_no_options(application_json: Path) -> None: ("--output {contract_name}.py", "hello_world_app.py"), ("-l python", "hello_world_app_client.py"), ("-o client.ts --language python", "client.ts"), + ("-o client.py --language python --version 1.1.2", "client.py"), + ("-l python -v 1.1.0", "hello_world_app_client.py"), ], ) -def test_generate_client_python(application_json: Path, options: str, expected_output_path: Path) -> None: +def test_generate_client_python( + proc_mock: ProcMock, + application_json: Path, + options: str, + expected_output_path: Path, + request: pytest.FixtureRequest, +) -> None: + proc_mock.should_bad_exit_on(["poetry", "show", PYTHON_PYPI_PACKAGE, "--tree"]) + proc_mock.should_bad_exit_on(["pipx", "list", "--short"]) + result = invoke(f"generate client {options} {application_json.name}", cwd=application_json.parent) + assert result.exit_code == 0 + verify( + _normalize_output(result.output), + namer=PyTestNamer(request), + options=NamerFactory.with_parameters(*options.split()), + ) + version = options.split()[-1] if "--version" in options or "-v" in options else None + assert len(proc_mock.called) == 4 # noqa: PLR2004 + assert ( + proc_mock.called[3].command + == _get_python_generate_command(version, application_json, expected_output_path).split() + ) + + +@pytest.mark.usefixtures("proc_mock") +def test_python_generator_is_installed_in_project(application_json: Path, proc_mock: ProcMock) -> None: + proc_mock.set_output( + ["poetry", "show", PYTHON_PYPI_PACKAGE, "--tree"], + output=[f"{PYTHON_PYPI_PACKAGE} 1.1.2 Algorand typed client Generator", "└── algokit-utils 2.2.1"], + ) + + result = invoke(f"generate client -o client.py -l python {application_json.name}", cwd=application_json.parent) assert result.exit_code == 0 - verify(_normalize_output(result.output), options=NamerFactory.with_parameters(*options.split())) - assert (application_json.parent / expected_output_path).exists() - assert (application_json.parent / expected_output_path).read_text() + verify(_normalize_output(result.output)) + + +@pytest.mark.usefixtures("proc_mock") +def test_python_generator_is_installed_globally(application_json: Path, proc_mock: ProcMock) -> None: + proc_mock.should_bad_exit_on(["poetry", "show", PYTHON_PYPI_PACKAGE, "--tree"]) + proc_mock.set_output( + ["pipx", "list", "--short"], + output=["algokit 1.13.0", "poetry 1.6.1", f"{PYTHON_PYPI_PACKAGE} 1.1.2"], + ) + + result = invoke(f"generate client -o client.py -l python {application_json.name}", cwd=application_json.parent) + + assert result.exit_code == 0 + verify(_normalize_output(result.output)) + + +@pytest.mark.usefixtures("proc_mock") +def test_python_generator_version_is_not_installed_anywhere(application_json: Path, proc_mock: ProcMock) -> None: + proc_mock.set_output( + ["poetry", "show", PYTHON_PYPI_PACKAGE, "--tree"], + output=[f"{PYTHON_PYPI_PACKAGE} 1.1.2 Algorand typed client Generator", "└── algokit-utils 2.2.1"], + ) + proc_mock.set_output( + ["pipx", "list", "--short"], + output=["algokit 1.13.0", "poetry 1.6.1", f"{PYTHON_PYPI_PACKAGE} 1.1.2"], + ) + + result = invoke( + f"generate client --version 1.2.0 -o client.py -l python {application_json.name}", cwd=application_json.parent + ) + + assert result.exit_code == 0 + verify(_normalize_output(result.output)) + + +@pytest.mark.usefixtures("proc_mock") +def test_pipx_missing(application_json: Path, mocker: MockerFixture, proc_mock: ProcMock) -> None: + proc_mock.should_bad_exit_on(["poetry", "show", PYTHON_PYPI_PACKAGE, "--tree"]) + mocker.patch("algokit.core.utils.get_candidate_pipx_commands", return_value=[]) + result = invoke(f"generate client -o client.py -l python {application_json.name}", cwd=application_json.parent) + + assert result.exit_code == 1 + verify(_normalize_output(result.output)) @pytest.mark.parametrize( @@ -99,13 +197,18 @@ def test_generate_client_python(application_json: Path, options: str, expected_o ("-o client.py", "client.py"), ], ) -def test_generate_client_python_arc32_filename(arc32_json: Path, options: str, expected_output_path: Path) -> None: +def test_generate_client_python_arc32_filename( + proc_mock: ProcMock, arc32_json: Path, options: str, expected_output_path: Path +) -> None: + proc_mock.should_bad_exit_on(["poetry", "show", PYTHON_PYPI_PACKAGE, "--tree"]) + proc_mock.should_bad_exit_on(["pipx", "list", "--short"]) + result = invoke(f"generate client {options} {arc32_json.name}", cwd=arc32_json.parent) assert result.exit_code == 0 verify(_normalize_output(result.output), options=NamerFactory.with_parameters(*options.split())) - assert (arc32_json.parent / expected_output_path).exists() - assert (arc32_json.parent / expected_output_path).read_text() + assert len(proc_mock.called) == 4 # noqa: PLR2004 + assert proc_mock.called[3].command == _get_python_generate_command(None, arc32_json, expected_output_path).split() @pytest.mark.usefixtures("mock_platform_system") @@ -116,6 +219,8 @@ def test_generate_client_python_arc32_filename(arc32_json: Path, options: str, e ("--output {contract_name}.ts", "HelloWorldApp.ts"), ("-l typescript", "HelloWorldAppClient.ts"), ("-o client.py --language typescript", "client.py"), + ("-o client.ts --language typescript --version 3.0.0", "client.ts"), + ("-l typescript -v 2.6.0", "HelloWorldAppClient.ts"), ], ) def test_generate_client_typescript( @@ -125,21 +230,80 @@ def test_generate_client_typescript( expected_output_path: Path, request: pytest.FixtureRequest, ) -> None: + npm_command = _get_npm_command() + proc_mock.should_bad_exit_on([npm_command, "ls"]) + proc_mock.should_bad_exit_on([npm_command, "ls", "--global"]) + result = invoke(f"generate client {options} {application_json.name}", cwd=application_json.parent) + assert result.exit_code == 0 verify( _normalize_output(result.output), namer=PyTestNamer(request), options=NamerFactory.with_parameters(*options.split()), ) - npx = _get_npx_command() - assert len(proc_mock.called) == 1 + version = options.split()[-1] if "--version" in options or "-v" in options else "latest" + assert len(proc_mock.called) == 3 # noqa: PLR2004 assert ( - proc_mock.called[0].command - == f"{npx} --yes {TYPESCRIPT_NPX_PACKAGE} generate -a {application_json} -o {expected_output_path}".split() + proc_mock.called[2].command + == _get_typescript_generate_command(version, application_json, expected_output_path).split() + ) + + +@pytest.mark.usefixtures("mock_platform_system") +def test_typescript_generator_is_installed_in_project( + application_json: Path, proc_mock: ProcMock, request: pytest.FixtureRequest +) -> None: + proc_mock.set_output( + [_get_npm_command(), "ls"], + output=["/Users/user/my-project", "├── test@1.2.3", f"└── {TYPESCRIPT_NPM_PACKAGE}@1.1.2"], + ) + + result = invoke(f"generate client -o client.py -l typescript {application_json.name}", cwd=application_json.parent) + + assert result.exit_code == 0 + verify(_normalize_output(result.output), namer=PyTestNamer(request)) + + +@pytest.mark.usefixtures("mock_platform_system") +def test_typescript_generator_is_installed_globally( + application_json: Path, proc_mock: ProcMock, request: pytest.FixtureRequest +) -> None: + proc_mock.should_bad_exit_on([_get_npm_command(), "ls"]) + proc_mock.set_output( + [_get_npm_command(), "--global", "ls"], + output=["/Users/user/.nvm/versions/node/v20.11.0/lib", "├── test@1.2.3", f"└── {TYPESCRIPT_NPM_PACKAGE}@1.1.2"], ) + result = invoke(f"generate client -o client.py -l typescript {application_json.name}", cwd=application_json.parent) + assert result.exit_code == 0 + verify(_normalize_output(result.output), namer=PyTestNamer(request)) + + +@pytest.mark.usefixtures("mock_platform_system") +def test_typescript_generator_version_is_not_installed_anywhere( + application_json: Path, proc_mock: ProcMock, request: pytest.FixtureRequest +) -> None: + proc_mock.set_output( + [_get_npm_command(), "ls"], + output=["/Users/user/my-project", "├── test@1.2.3", f"└── {TYPESCRIPT_NPM_PACKAGE}@1.1.2"], + ) + proc_mock.set_output( + [_get_npm_command(), "--global", "ls"], + output=["/Users/user/.nvm/versions/node/v20.11.0/lib", "├── test@1.2.3", f"└── {TYPESCRIPT_NPM_PACKAGE}@1.1.2"], + ) + + result = invoke( + f"generate client --version 1.2.0 -o client.py -l typescript {application_json.name}", + cwd=application_json.parent, + ) + + assert result.exit_code == 0 + verify(_normalize_output(result.output), namer=PyTestNamer(request)) + + +@pytest.mark.usefixtures("proc_mock") def test_npx_missing(application_json: Path, which_mock: WhichMock) -> None: which_mock.remove("npx") result = invoke(f"generate client -o client.ts {application_json.name}", cwd=application_json.parent) @@ -154,18 +318,19 @@ def test_npx_failed( application_json: Path, request: pytest.FixtureRequest, ) -> None: - npx = _get_npx_command() - proc_mock.should_bad_exit_on(f"{npx} --yes {TYPESCRIPT_NPX_PACKAGE} generate -a {application_json} -o client.ts") + proc_mock.should_bad_exit_on(_get_typescript_generate_command("latest", application_json, Path("client.ts"))) result = invoke(f"generate client -o client.ts {application_json.name}", cwd=application_json.parent) - assert result.exit_code == 1 + assert result.exit_code == -1 verify( _normalize_output(result.output), namer=PyTestNamer(request), ) -def test_generate_client_recursive(cwd: Path, dir_with_app_spec_factory: DirWithAppSpecFactory) -> None: +def test_generate_client_recursive( + proc_mock: ProcMock, cwd: Path, dir_with_app_spec_factory: DirWithAppSpecFactory +) -> None: dir_paths = [ cwd / "dir1", cwd / "dir2", @@ -178,18 +343,19 @@ def test_generate_client_recursive(cwd: Path, dir_with_app_spec_factory: DirWith assert result.exit_code == 0 verify(_normalize_output(result.output)) - for dir_path in dir_paths: + for index, dir_path in enumerate(dir_paths): output_path = dir_path / "output.py" - assert output_path.exists() - assert output_path.read_text() + proc_mock.called[index].command[-1] = str(output_path) +@pytest.mark.usefixtures("proc_mock") def test_generate_client_no_app_spec_found(cwd: Path) -> None: result = invoke("generate client -o output.py .", cwd=cwd) assert result.exit_code == 1 verify(_normalize_output(result.output)) +@pytest.mark.usefixtures("proc_mock") def test_generate_client_output_path_is_dir(application_json: Path) -> None: cwd = application_json.parent (cwd / "hello_world_app.py").mkdir() diff --git a/tests/generate/test_generate_client.test_generate_client_no_app_spec_found.approved.txt b/tests/generate/test_generate_client.test_generate_client_no_app_spec_found.approved.txt index 7af4ba90..1ce175b4 100644 --- a/tests/generate/test_generate_client.test_generate_client_no_app_spec_found.approved.txt +++ b/tests/generate/test_generate_client.test_generate_client_no_app_spec_found.approved.txt @@ -1 +1,5 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'poetry show algokit-client-generator --tree' in '{current_working_directory}' +DEBUG: poetry: STDOUT +DEBUG: poetry: STDERR Error: No app specs found diff --git a/tests/generate/test_generate_client.test_generate_client_output_path_is_dir.approved.txt b/tests/generate/test_generate_client.test_generate_client_output_path_is_dir.approved.txt index 5b73fa2c..1f0b3881 100644 --- a/tests/generate/test_generate_client.test_generate_client_output_path_is_dir.approved.txt +++ b/tests/generate/test_generate_client.test_generate_client_output_path_is_dir.approved.txt @@ -1 +1,5 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'poetry show algokit-client-generator --tree' in '{current_working_directory}' +DEBUG: poetry: STDOUT +DEBUG: poetry: STDERR ERROR: Could not output to hello_world_app.py as it already exists and is a directory diff --git a/tests/generate/test_generate_client.test_generate_client_python.--output.{contract_name}.py.approved.txt b/tests/generate/test_generate_client.test_generate_client_python.--output.{contract_name}.py.approved.txt deleted file mode 100644 index e981513e..00000000 --- a/tests/generate/test_generate_client.test_generate_client_python.--output.{contract_name}.py.approved.txt +++ /dev/null @@ -1,2 +0,0 @@ -Generating Python client code for application specified in {current_working_directory}/application.json and writing to hello_world_app.py -Output typed client for HelloWorldApp to hello_world_app.py diff --git a/tests/generate/test_generate_client.test_generate_client_python.-l.python.approved.txt b/tests/generate/test_generate_client.test_generate_client_python.-l.python.approved.txt deleted file mode 100644 index d49b7629..00000000 --- a/tests/generate/test_generate_client.test_generate_client_python.-l.python.approved.txt +++ /dev/null @@ -1,2 +0,0 @@ -Generating Python client code for application specified in {current_working_directory}/application.json and writing to hello_world_app_client.py -Output typed client for HelloWorldApp to hello_world_app_client.py diff --git a/tests/generate/test_generate_client.test_generate_client_python.-o.client.py.approved.txt b/tests/generate/test_generate_client.test_generate_client_python.-o.client.py.approved.txt deleted file mode 100644 index c417eabe..00000000 --- a/tests/generate/test_generate_client.test_generate_client_python.-o.client.py.approved.txt +++ /dev/null @@ -1,2 +0,0 @@ -Generating Python client code for application specified in {current_working_directory}/application.json and writing to client.py -Output typed client for HelloWorldApp to client.py diff --git a/tests/generate/test_generate_client.test_generate_client_python.-o.client.ts.--language.python.approved.txt b/tests/generate/test_generate_client.test_generate_client_python.-o.client.ts.--language.python.approved.txt deleted file mode 100644 index 402012dc..00000000 --- a/tests/generate/test_generate_client.test_generate_client_python.-o.client.ts.--language.python.approved.txt +++ /dev/null @@ -1,2 +0,0 @@ -Generating Python client code for application specified in {current_working_directory}/application.json and writing to client.ts -Output typed client for HelloWorldApp to client.ts diff --git a/tests/generate/test_generate_client.test_generate_client_python[--output {contract_name}.py-hello_world_app.py].approved.txt b/tests/generate/test_generate_client.test_generate_client_python[--output {contract_name}.py-hello_world_app.py].approved.txt new file mode 100644 index 00000000..13b76eaa --- /dev/null +++ b/tests/generate/test_generate_client.test_generate_client_python[--output {contract_name}.py-hello_world_app.py].approved.txt @@ -0,0 +1,18 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'poetry show algokit-client-generator --tree' in '{current_working_directory}' +DEBUG: poetry: STDOUT +DEBUG: poetry: STDERR +DEBUG: Running 'pipx --version' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'pipx list --short' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +DEBUG: No matching installed client generator found, run client generator via pipx +Generating Python client code for application specified in {current_working_directory}/application.json and writing to hello_world_app.py +DEBUG: Running 'pipx run --spec=algokit-client-generator algokitgen-py -a {current_working_directory}/application.json -o hello_world_app.py' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_generate_client_python[-l python -v 1.1.0-hello_world_app_client.py].approved.txt b/tests/generate/test_generate_client.test_generate_client_python[-l python -v 1.1.0-hello_world_app_client.py].approved.txt new file mode 100644 index 00000000..e2d9e075 --- /dev/null +++ b/tests/generate/test_generate_client.test_generate_client_python[-l python -v 1.1.0-hello_world_app_client.py].approved.txt @@ -0,0 +1,18 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'poetry show algokit-client-generator --tree' in '{current_working_directory}' +DEBUG: poetry: STDOUT +DEBUG: poetry: STDERR +DEBUG: Running 'pipx --version' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'pipx list --short' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +DEBUG: No matching installed client generator found, run client generator via pipx +Generating Python client code for application specified in {current_working_directory}/application.json and writing to hello_world_app_client.py +DEBUG: Running 'pipx run --spec=algokit-client-generator==1.1.0 algokitgen-py -a {current_working_directory}/application.json -o hello_world_app_client.py' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_generate_client_python[-l python-hello_world_app_client.py].approved.txt b/tests/generate/test_generate_client.test_generate_client_python[-l python-hello_world_app_client.py].approved.txt new file mode 100644 index 00000000..588fc447 --- /dev/null +++ b/tests/generate/test_generate_client.test_generate_client_python[-l python-hello_world_app_client.py].approved.txt @@ -0,0 +1,18 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'poetry show algokit-client-generator --tree' in '{current_working_directory}' +DEBUG: poetry: STDOUT +DEBUG: poetry: STDERR +DEBUG: Running 'pipx --version' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'pipx list --short' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +DEBUG: No matching installed client generator found, run client generator via pipx +Generating Python client code for application specified in {current_working_directory}/application.json and writing to hello_world_app_client.py +DEBUG: Running 'pipx run --spec=algokit-client-generator algokitgen-py -a {current_working_directory}/application.json -o hello_world_app_client.py' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_generate_client_python[-o client.py --language python --version 1.1.2-client.py].approved.txt b/tests/generate/test_generate_client.test_generate_client_python[-o client.py --language python --version 1.1.2-client.py].approved.txt new file mode 100644 index 00000000..fc72588b --- /dev/null +++ b/tests/generate/test_generate_client.test_generate_client_python[-o client.py --language python --version 1.1.2-client.py].approved.txt @@ -0,0 +1,18 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'poetry show algokit-client-generator --tree' in '{current_working_directory}' +DEBUG: poetry: STDOUT +DEBUG: poetry: STDERR +DEBUG: Running 'pipx --version' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'pipx list --short' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +DEBUG: No matching installed client generator found, run client generator via pipx +Generating Python client code for application specified in {current_working_directory}/application.json and writing to client.py +DEBUG: Running 'pipx run --spec=algokit-client-generator==1.1.2 algokitgen-py -a {current_working_directory}/application.json -o client.py' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_generate_client_python[-o client.py-client.py].approved.txt b/tests/generate/test_generate_client.test_generate_client_python[-o client.py-client.py].approved.txt new file mode 100644 index 00000000..05a1fb6c --- /dev/null +++ b/tests/generate/test_generate_client.test_generate_client_python[-o client.py-client.py].approved.txt @@ -0,0 +1,18 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'poetry show algokit-client-generator --tree' in '{current_working_directory}' +DEBUG: poetry: STDOUT +DEBUG: poetry: STDERR +DEBUG: Running 'pipx --version' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'pipx list --short' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +DEBUG: No matching installed client generator found, run client generator via pipx +Generating Python client code for application specified in {current_working_directory}/application.json and writing to client.py +DEBUG: Running 'pipx run --spec=algokit-client-generator algokitgen-py -a {current_working_directory}/application.json -o client.py' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_generate_client_python[-o client.ts --language python-client.ts].approved.txt b/tests/generate/test_generate_client.test_generate_client_python[-o client.ts --language python-client.ts].approved.txt new file mode 100644 index 00000000..ce6a82e6 --- /dev/null +++ b/tests/generate/test_generate_client.test_generate_client_python[-o client.ts --language python-client.ts].approved.txt @@ -0,0 +1,18 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'poetry show algokit-client-generator --tree' in '{current_working_directory}' +DEBUG: poetry: STDOUT +DEBUG: poetry: STDERR +DEBUG: Running 'pipx --version' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'pipx list --short' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +DEBUG: No matching installed client generator found, run client generator via pipx +Generating Python client code for application specified in {current_working_directory}/application.json and writing to client.ts +DEBUG: Running 'pipx run --spec=algokit-client-generator algokitgen-py -a {current_working_directory}/application.json -o client.ts' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_generate_client_python_arc32_filename.-o.client.py.approved.txt b/tests/generate/test_generate_client.test_generate_client_python_arc32_filename.-o.client.py.approved.txt index 85d4eacb..c5c7930f 100644 --- a/tests/generate/test_generate_client.test_generate_client_python_arc32_filename.-o.client.py.approved.txt +++ b/tests/generate/test_generate_client.test_generate_client_python_arc32_filename.-o.client.py.approved.txt @@ -1,2 +1,18 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'poetry show algokit-client-generator --tree' in '{current_working_directory}' +DEBUG: poetry: STDOUT +DEBUG: poetry: STDERR +DEBUG: Running 'pipx --version' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'pipx list --short' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +DEBUG: No matching installed client generator found, run client generator via pipx Generating Python client code for application specified in {current_working_directory}/app.arc32.json and writing to client.py -Output typed client for HelloWorldApp to client.py +DEBUG: Running 'pipx run --spec=algokit-client-generator algokitgen-py -a {current_working_directory}/app.arc32.json -o client.py' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_generate_client_recursive.approved.txt b/tests/generate/test_generate_client.test_generate_client_recursive.approved.txt index 56634b10..45f41d9a 100644 --- a/tests/generate/test_generate_client.test_generate_client_recursive.approved.txt +++ b/tests/generate/test_generate_client.test_generate_client_recursive.approved.txt @@ -1,6 +1,22 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'poetry show algokit-client-generator --tree' in '{current_working_directory}' +DEBUG: poetry: STDOUT +DEBUG: poetry: STDERR Generating Python client code for application specified in {current_working_directory}/dir1/application.json and writing to {current_working_directory}/dir1/output.py -Output typed client for HelloWorldApp to {current_working_directory}/dir1/output.py +DEBUG: Running 'poetry run algokitgen-py -a {current_working_directory}/dir1/application.json -o {current_working_directory}/dir1/output.py' in '{current_working_directory}' +DEBUG: poetry: STDOUT +DEBUG: poetry: STDERR +STDOUT +STDERR Generating Python client code for application specified in {current_working_directory}/dir2/application.json and writing to {current_working_directory}/dir2/output.py -Output typed client for HelloWorldApp to {current_working_directory}/dir2/output.py +DEBUG: Running 'poetry run algokitgen-py -a {current_working_directory}/dir2/application.json -o {current_working_directory}/dir2/output.py' in '{current_working_directory}' +DEBUG: poetry: STDOUT +DEBUG: poetry: STDERR +STDOUT +STDERR Generating Python client code for application specified in {current_working_directory}/dir2/sub_dir/application.json and writing to {current_working_directory}/dir2/sub_dir/output.py -Output typed client for HelloWorldApp to {current_working_directory}/dir2/sub_dir/output.py +DEBUG: Running 'poetry run algokitgen-py -a {current_working_directory}/dir2/sub_dir/application.json -o {current_working_directory}/dir2/sub_dir/output.py' in '{current_working_directory}' +DEBUG: poetry: STDOUT +DEBUG: poetry: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_generate_client_typescript[linux---output {contract_name}.ts-HelloWorldApp.ts].approved.txt b/tests/generate/test_generate_client.test_generate_client_typescript[linux---output {contract_name}.ts-HelloWorldApp.ts].approved.txt index 170b6fbb..ff24ab8c 100644 --- a/tests/generate/test_generate_client.test_generate_client_typescript[linux---output {contract_name}.ts-HelloWorldApp.ts].approved.txt +++ b/tests/generate/test_generate_client.test_generate_client_typescript[linux---output {contract_name}.ts-HelloWorldApp.ts].approved.txt @@ -1,4 +1,15 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm --global ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: No matching installed client generator found, run client generator via npx Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to HelloWorldApp.ts -DEBUG: Running 'npx --yes {typed_client_package} generate -a {current_working_directory}/application.json -o HelloWorldApp.ts' in '{current_working_directory}' +DEBUG: Running 'npx --yes @algorandfoundation/algokit-client-generator@latest generate -a {current_working_directory}/application.json -o HelloWorldApp.ts' in '{current_working_directory}' DEBUG: npx: STDOUT DEBUG: npx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_generate_client_typescript[linux--l typescript -v 2.6.0-HelloWorldAppClient.ts].approved.txt b/tests/generate/test_generate_client.test_generate_client_typescript[linux--l typescript -v 2.6.0-HelloWorldAppClient.ts].approved.txt new file mode 100644 index 00000000..cfa808f7 --- /dev/null +++ b/tests/generate/test_generate_client.test_generate_client_typescript[linux--l typescript -v 2.6.0-HelloWorldAppClient.ts].approved.txt @@ -0,0 +1,15 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm --global ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: No matching installed client generator found, run client generator via npx +Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to HelloWorldAppClient.ts +DEBUG: Running 'npx --yes @algorandfoundation/algokit-client-generator@2.6.0 generate -a {current_working_directory}/application.json -o HelloWorldAppClient.ts' in '{current_working_directory}' +DEBUG: npx: STDOUT +DEBUG: npx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_generate_client_typescript[linux--l typescript-HelloWorldAppClient.ts].approved.txt b/tests/generate/test_generate_client.test_generate_client_typescript[linux--l typescript-HelloWorldAppClient.ts].approved.txt index d13f0f01..83a840e9 100644 --- a/tests/generate/test_generate_client.test_generate_client_typescript[linux--l typescript-HelloWorldAppClient.ts].approved.txt +++ b/tests/generate/test_generate_client.test_generate_client_typescript[linux--l typescript-HelloWorldAppClient.ts].approved.txt @@ -1,4 +1,15 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm --global ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: No matching installed client generator found, run client generator via npx Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to HelloWorldAppClient.ts -DEBUG: Running 'npx --yes {typed_client_package} generate -a {current_working_directory}/application.json -o HelloWorldAppClient.ts' in '{current_working_directory}' +DEBUG: Running 'npx --yes @algorandfoundation/algokit-client-generator@latest generate -a {current_working_directory}/application.json -o HelloWorldAppClient.ts' in '{current_working_directory}' DEBUG: npx: STDOUT DEBUG: npx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_generate_client_typescript[linux--o client.py --language typescript-client.py].approved.txt b/tests/generate/test_generate_client.test_generate_client_typescript[linux--o client.py --language typescript-client.py].approved.txt index bd45394a..04e27d39 100644 --- a/tests/generate/test_generate_client.test_generate_client_typescript[linux--o client.py --language typescript-client.py].approved.txt +++ b/tests/generate/test_generate_client.test_generate_client_typescript[linux--o client.py --language typescript-client.py].approved.txt @@ -1,4 +1,15 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm --global ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: No matching installed client generator found, run client generator via npx Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to client.py -DEBUG: Running 'npx --yes {typed_client_package} generate -a {current_working_directory}/application.json -o client.py' in '{current_working_directory}' +DEBUG: Running 'npx --yes @algorandfoundation/algokit-client-generator@latest generate -a {current_working_directory}/application.json -o client.py' in '{current_working_directory}' DEBUG: npx: STDOUT DEBUG: npx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_generate_client_typescript[linux--o client.ts --language typescript --version 3.0.0-client.ts].approved.txt b/tests/generate/test_generate_client.test_generate_client_typescript[linux--o client.ts --language typescript --version 3.0.0-client.ts].approved.txt new file mode 100644 index 00000000..fe44ef3b --- /dev/null +++ b/tests/generate/test_generate_client.test_generate_client_typescript[linux--o client.ts --language typescript --version 3.0.0-client.ts].approved.txt @@ -0,0 +1,15 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm --global ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: No matching installed client generator found, run client generator via npx +Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to client.ts +DEBUG: Running 'npx --yes @algorandfoundation/algokit-client-generator@3.0.0 generate -a {current_working_directory}/application.json -o client.ts' in '{current_working_directory}' +DEBUG: npx: STDOUT +DEBUG: npx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_generate_client_typescript[linux--o client.ts-client.ts].approved.txt b/tests/generate/test_generate_client.test_generate_client_typescript[linux--o client.ts-client.ts].approved.txt index ad17722c..69b5b171 100644 --- a/tests/generate/test_generate_client.test_generate_client_typescript[linux--o client.ts-client.ts].approved.txt +++ b/tests/generate/test_generate_client.test_generate_client_typescript[linux--o client.ts-client.ts].approved.txt @@ -1,4 +1,15 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm --global ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: No matching installed client generator found, run client generator via npx Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to client.ts -DEBUG: Running 'npx --yes {typed_client_package} generate -a {current_working_directory}/application.json -o client.ts' in '{current_working_directory}' +DEBUG: Running 'npx --yes @algorandfoundation/algokit-client-generator@latest generate -a {current_working_directory}/application.json -o client.ts' in '{current_working_directory}' DEBUG: npx: STDOUT DEBUG: npx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_generate_client_typescript[macOS---output {contract_name}.ts-HelloWorldApp.ts].approved.txt b/tests/generate/test_generate_client.test_generate_client_typescript[macOS---output {contract_name}.ts-HelloWorldApp.ts].approved.txt index 170b6fbb..ff24ab8c 100644 --- a/tests/generate/test_generate_client.test_generate_client_typescript[macOS---output {contract_name}.ts-HelloWorldApp.ts].approved.txt +++ b/tests/generate/test_generate_client.test_generate_client_typescript[macOS---output {contract_name}.ts-HelloWorldApp.ts].approved.txt @@ -1,4 +1,15 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm --global ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: No matching installed client generator found, run client generator via npx Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to HelloWorldApp.ts -DEBUG: Running 'npx --yes {typed_client_package} generate -a {current_working_directory}/application.json -o HelloWorldApp.ts' in '{current_working_directory}' +DEBUG: Running 'npx --yes @algorandfoundation/algokit-client-generator@latest generate -a {current_working_directory}/application.json -o HelloWorldApp.ts' in '{current_working_directory}' DEBUG: npx: STDOUT DEBUG: npx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_generate_client_typescript[macOS--l typescript -v 2.6.0-HelloWorldAppClient.ts].approved.txt b/tests/generate/test_generate_client.test_generate_client_typescript[macOS--l typescript -v 2.6.0-HelloWorldAppClient.ts].approved.txt new file mode 100644 index 00000000..cfa808f7 --- /dev/null +++ b/tests/generate/test_generate_client.test_generate_client_typescript[macOS--l typescript -v 2.6.0-HelloWorldAppClient.ts].approved.txt @@ -0,0 +1,15 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm --global ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: No matching installed client generator found, run client generator via npx +Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to HelloWorldAppClient.ts +DEBUG: Running 'npx --yes @algorandfoundation/algokit-client-generator@2.6.0 generate -a {current_working_directory}/application.json -o HelloWorldAppClient.ts' in '{current_working_directory}' +DEBUG: npx: STDOUT +DEBUG: npx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_generate_client_typescript[macOS--l typescript-HelloWorldAppClient.ts].approved.txt b/tests/generate/test_generate_client.test_generate_client_typescript[macOS--l typescript-HelloWorldAppClient.ts].approved.txt index d13f0f01..83a840e9 100644 --- a/tests/generate/test_generate_client.test_generate_client_typescript[macOS--l typescript-HelloWorldAppClient.ts].approved.txt +++ b/tests/generate/test_generate_client.test_generate_client_typescript[macOS--l typescript-HelloWorldAppClient.ts].approved.txt @@ -1,4 +1,15 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm --global ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: No matching installed client generator found, run client generator via npx Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to HelloWorldAppClient.ts -DEBUG: Running 'npx --yes {typed_client_package} generate -a {current_working_directory}/application.json -o HelloWorldAppClient.ts' in '{current_working_directory}' +DEBUG: Running 'npx --yes @algorandfoundation/algokit-client-generator@latest generate -a {current_working_directory}/application.json -o HelloWorldAppClient.ts' in '{current_working_directory}' DEBUG: npx: STDOUT DEBUG: npx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_generate_client_typescript[macOS--o client.py --language typescript-client.py].approved.txt b/tests/generate/test_generate_client.test_generate_client_typescript[macOS--o client.py --language typescript-client.py].approved.txt index bd45394a..04e27d39 100644 --- a/tests/generate/test_generate_client.test_generate_client_typescript[macOS--o client.py --language typescript-client.py].approved.txt +++ b/tests/generate/test_generate_client.test_generate_client_typescript[macOS--o client.py --language typescript-client.py].approved.txt @@ -1,4 +1,15 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm --global ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: No matching installed client generator found, run client generator via npx Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to client.py -DEBUG: Running 'npx --yes {typed_client_package} generate -a {current_working_directory}/application.json -o client.py' in '{current_working_directory}' +DEBUG: Running 'npx --yes @algorandfoundation/algokit-client-generator@latest generate -a {current_working_directory}/application.json -o client.py' in '{current_working_directory}' DEBUG: npx: STDOUT DEBUG: npx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_generate_client_typescript[macOS--o client.ts --language typescript --version 3.0.0-client.ts].approved.txt b/tests/generate/test_generate_client.test_generate_client_typescript[macOS--o client.ts --language typescript --version 3.0.0-client.ts].approved.txt new file mode 100644 index 00000000..fe44ef3b --- /dev/null +++ b/tests/generate/test_generate_client.test_generate_client_typescript[macOS--o client.ts --language typescript --version 3.0.0-client.ts].approved.txt @@ -0,0 +1,15 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm --global ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: No matching installed client generator found, run client generator via npx +Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to client.ts +DEBUG: Running 'npx --yes @algorandfoundation/algokit-client-generator@3.0.0 generate -a {current_working_directory}/application.json -o client.ts' in '{current_working_directory}' +DEBUG: npx: STDOUT +DEBUG: npx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_generate_client_typescript[macOS--o client.ts-client.ts].approved.txt b/tests/generate/test_generate_client.test_generate_client_typescript[macOS--o client.ts-client.ts].approved.txt index ad17722c..69b5b171 100644 --- a/tests/generate/test_generate_client.test_generate_client_typescript[macOS--o client.ts-client.ts].approved.txt +++ b/tests/generate/test_generate_client.test_generate_client_typescript[macOS--o client.ts-client.ts].approved.txt @@ -1,4 +1,15 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm --global ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: No matching installed client generator found, run client generator via npx Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to client.ts -DEBUG: Running 'npx --yes {typed_client_package} generate -a {current_working_directory}/application.json -o client.ts' in '{current_working_directory}' +DEBUG: Running 'npx --yes @algorandfoundation/algokit-client-generator@latest generate -a {current_working_directory}/application.json -o client.ts' in '{current_working_directory}' DEBUG: npx: STDOUT DEBUG: npx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_generate_client_typescript[windows---output {contract_name}.ts-HelloWorldApp.ts].approved.txt b/tests/generate/test_generate_client.test_generate_client_typescript[windows---output {contract_name}.ts-HelloWorldApp.ts].approved.txt index 8741d746..1d9197c9 100644 --- a/tests/generate/test_generate_client.test_generate_client_typescript[windows---output {contract_name}.ts-HelloWorldApp.ts].approved.txt +++ b/tests/generate/test_generate_client.test_generate_client_typescript[windows---output {contract_name}.ts-HelloWorldApp.ts].approved.txt @@ -1,4 +1,15 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm.cmd ls' in '{current_working_directory}' +DEBUG: npm.cmd: STDOUT +DEBUG: npm.cmd: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm.cmd --global ls' in '{current_working_directory}' +DEBUG: npm.cmd: STDOUT +DEBUG: npm.cmd: STDERR +DEBUG: No matching installed client generator found, run client generator via npx Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to HelloWorldApp.ts -DEBUG: Running 'npx.cmd --yes {typed_client_package} generate -a {current_working_directory}/application.json -o HelloWorldApp.ts' in '{current_working_directory}' +DEBUG: Running 'npx.cmd --yes @algorandfoundation/algokit-client-generator@latest generate -a {current_working_directory}/application.json -o HelloWorldApp.ts' in '{current_working_directory}' DEBUG: npx.cmd: STDOUT DEBUG: npx.cmd: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_generate_client_typescript[windows--l typescript -v 2.6.0-HelloWorldAppClient.ts].approved.txt b/tests/generate/test_generate_client.test_generate_client_typescript[windows--l typescript -v 2.6.0-HelloWorldAppClient.ts].approved.txt new file mode 100644 index 00000000..7845ad3e --- /dev/null +++ b/tests/generate/test_generate_client.test_generate_client_typescript[windows--l typescript -v 2.6.0-HelloWorldAppClient.ts].approved.txt @@ -0,0 +1,15 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm.cmd ls' in '{current_working_directory}' +DEBUG: npm.cmd: STDOUT +DEBUG: npm.cmd: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm.cmd --global ls' in '{current_working_directory}' +DEBUG: npm.cmd: STDOUT +DEBUG: npm.cmd: STDERR +DEBUG: No matching installed client generator found, run client generator via npx +Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to HelloWorldAppClient.ts +DEBUG: Running 'npx.cmd --yes @algorandfoundation/algokit-client-generator@2.6.0 generate -a {current_working_directory}/application.json -o HelloWorldAppClient.ts' in '{current_working_directory}' +DEBUG: npx.cmd: STDOUT +DEBUG: npx.cmd: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_generate_client_typescript[windows--l typescript-HelloWorldAppClient.ts].approved.txt b/tests/generate/test_generate_client.test_generate_client_typescript[windows--l typescript-HelloWorldAppClient.ts].approved.txt index 0af13a6f..c5773ad5 100644 --- a/tests/generate/test_generate_client.test_generate_client_typescript[windows--l typescript-HelloWorldAppClient.ts].approved.txt +++ b/tests/generate/test_generate_client.test_generate_client_typescript[windows--l typescript-HelloWorldAppClient.ts].approved.txt @@ -1,4 +1,15 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm.cmd ls' in '{current_working_directory}' +DEBUG: npm.cmd: STDOUT +DEBUG: npm.cmd: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm.cmd --global ls' in '{current_working_directory}' +DEBUG: npm.cmd: STDOUT +DEBUG: npm.cmd: STDERR +DEBUG: No matching installed client generator found, run client generator via npx Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to HelloWorldAppClient.ts -DEBUG: Running 'npx.cmd --yes {typed_client_package} generate -a {current_working_directory}/application.json -o HelloWorldAppClient.ts' in '{current_working_directory}' +DEBUG: Running 'npx.cmd --yes @algorandfoundation/algokit-client-generator@latest generate -a {current_working_directory}/application.json -o HelloWorldAppClient.ts' in '{current_working_directory}' DEBUG: npx.cmd: STDOUT DEBUG: npx.cmd: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_generate_client_typescript[windows--o client.py --language typescript-client.py].approved.txt b/tests/generate/test_generate_client.test_generate_client_typescript[windows--o client.py --language typescript-client.py].approved.txt index 80b07748..4a8eb604 100644 --- a/tests/generate/test_generate_client.test_generate_client_typescript[windows--o client.py --language typescript-client.py].approved.txt +++ b/tests/generate/test_generate_client.test_generate_client_typescript[windows--o client.py --language typescript-client.py].approved.txt @@ -1,4 +1,15 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm.cmd ls' in '{current_working_directory}' +DEBUG: npm.cmd: STDOUT +DEBUG: npm.cmd: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm.cmd --global ls' in '{current_working_directory}' +DEBUG: npm.cmd: STDOUT +DEBUG: npm.cmd: STDERR +DEBUG: No matching installed client generator found, run client generator via npx Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to client.py -DEBUG: Running 'npx.cmd --yes {typed_client_package} generate -a {current_working_directory}/application.json -o client.py' in '{current_working_directory}' +DEBUG: Running 'npx.cmd --yes @algorandfoundation/algokit-client-generator@latest generate -a {current_working_directory}/application.json -o client.py' in '{current_working_directory}' DEBUG: npx.cmd: STDOUT DEBUG: npx.cmd: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_generate_client_typescript[windows--o client.ts --language typescript --version 3.0.0-client.ts].approved.txt b/tests/generate/test_generate_client.test_generate_client_typescript[windows--o client.ts --language typescript --version 3.0.0-client.ts].approved.txt new file mode 100644 index 00000000..aadf3a8c --- /dev/null +++ b/tests/generate/test_generate_client.test_generate_client_typescript[windows--o client.ts --language typescript --version 3.0.0-client.ts].approved.txt @@ -0,0 +1,15 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm.cmd ls' in '{current_working_directory}' +DEBUG: npm.cmd: STDOUT +DEBUG: npm.cmd: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm.cmd --global ls' in '{current_working_directory}' +DEBUG: npm.cmd: STDOUT +DEBUG: npm.cmd: STDERR +DEBUG: No matching installed client generator found, run client generator via npx +Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to client.ts +DEBUG: Running 'npx.cmd --yes @algorandfoundation/algokit-client-generator@3.0.0 generate -a {current_working_directory}/application.json -o client.ts' in '{current_working_directory}' +DEBUG: npx.cmd: STDOUT +DEBUG: npx.cmd: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_generate_client_typescript[windows--o client.ts-client.ts].approved.txt b/tests/generate/test_generate_client.test_generate_client_typescript[windows--o client.ts-client.ts].approved.txt index 7a908c08..51d0c653 100644 --- a/tests/generate/test_generate_client.test_generate_client_typescript[windows--o client.ts-client.ts].approved.txt +++ b/tests/generate/test_generate_client.test_generate_client_typescript[windows--o client.ts-client.ts].approved.txt @@ -1,4 +1,15 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm.cmd ls' in '{current_working_directory}' +DEBUG: npm.cmd: STDOUT +DEBUG: npm.cmd: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm.cmd --global ls' in '{current_working_directory}' +DEBUG: npm.cmd: STDOUT +DEBUG: npm.cmd: STDERR +DEBUG: No matching installed client generator found, run client generator via npx Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to client.ts -DEBUG: Running 'npx.cmd --yes {typed_client_package} generate -a {current_working_directory}/application.json -o client.ts' in '{current_working_directory}' +DEBUG: Running 'npx.cmd --yes @algorandfoundation/algokit-client-generator@latest generate -a {current_working_directory}/application.json -o client.ts' in '{current_working_directory}' DEBUG: npx.cmd: STDOUT DEBUG: npx.cmd: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_npx_failed[linux].approved.txt b/tests/generate/test_generate_client.test_npx_failed[linux].approved.txt index 59219e27..bc65b201 100644 --- a/tests/generate/test_generate_client.test_npx_failed[linux].approved.txt +++ b/tests/generate/test_generate_client.test_npx_failed[linux].approved.txt @@ -1,5 +1,16 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm --global ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: No matching installed client generator found, run client generator via npx Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to client.ts -DEBUG: Running 'npx --yes {typed_client_package} generate -a {current_working_directory}/application.json -o client.ts' in '{current_working_directory}' +DEBUG: Running 'npx --yes @algorandfoundation/algokit-client-generator@latest generate -a {current_working_directory}/application.json -o client.ts' in '{current_working_directory}' DEBUG: npx: STDOUT DEBUG: npx: STDERR -Error: Client generation failed for {current_working_directory}/application.json. +STDOUT +STDERR +Client generation failed for {current_working_directory}/application.json. diff --git a/tests/generate/test_generate_client.test_npx_failed[macOS].approved.txt b/tests/generate/test_generate_client.test_npx_failed[macOS].approved.txt index 59219e27..bc65b201 100644 --- a/tests/generate/test_generate_client.test_npx_failed[macOS].approved.txt +++ b/tests/generate/test_generate_client.test_npx_failed[macOS].approved.txt @@ -1,5 +1,16 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm --global ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: No matching installed client generator found, run client generator via npx Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to client.ts -DEBUG: Running 'npx --yes {typed_client_package} generate -a {current_working_directory}/application.json -o client.ts' in '{current_working_directory}' +DEBUG: Running 'npx --yes @algorandfoundation/algokit-client-generator@latest generate -a {current_working_directory}/application.json -o client.ts' in '{current_working_directory}' DEBUG: npx: STDOUT DEBUG: npx: STDERR -Error: Client generation failed for {current_working_directory}/application.json. +STDOUT +STDERR +Client generation failed for {current_working_directory}/application.json. diff --git a/tests/generate/test_generate_client.test_npx_failed[windows].approved.txt b/tests/generate/test_generate_client.test_npx_failed[windows].approved.txt index 6ede8147..2b574af9 100644 --- a/tests/generate/test_generate_client.test_npx_failed[windows].approved.txt +++ b/tests/generate/test_generate_client.test_npx_failed[windows].approved.txt @@ -1,5 +1,16 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm.cmd ls' in '{current_working_directory}' +DEBUG: npm.cmd: STDOUT +DEBUG: npm.cmd: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm.cmd --global ls' in '{current_working_directory}' +DEBUG: npm.cmd: STDOUT +DEBUG: npm.cmd: STDERR +DEBUG: No matching installed client generator found, run client generator via npx Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to client.ts -DEBUG: Running 'npx.cmd --yes {typed_client_package} generate -a {current_working_directory}/application.json -o client.ts' in '{current_working_directory}' +DEBUG: Running 'npx.cmd --yes @algorandfoundation/algokit-client-generator@latest generate -a {current_working_directory}/application.json -o client.ts' in '{current_working_directory}' DEBUG: npx.cmd: STDOUT DEBUG: npx.cmd: STDERR -Error: Client generation failed for {current_working_directory}/application.json. +STDOUT +STDERR +Client generation failed for {current_working_directory}/application.json. diff --git a/tests/generate/test_generate_client.test_npx_missing.approved.txt b/tests/generate/test_generate_client.test_npx_missing.approved.txt index 493ef2cc..f766add5 100644 --- a/tests/generate/test_generate_client.test_npx_missing.approved.txt +++ b/tests/generate/test_generate_client.test_npx_missing.approved.txt @@ -1 +1 @@ -Error: Typescript generator requires Node.js and npx to be installed. +Error: Unable to find npx install so that the `@algorandfoundation/algokit-client-generator` can be run; please install npx via https://www.npmjs.com/package/npx and then try `algokit generate client ...` again. diff --git a/tests/generate/test_generate_client.test_pipx_missing.approved.txt b/tests/generate/test_generate_client.test_pipx_missing.approved.txt new file mode 100644 index 00000000..ba509bec --- /dev/null +++ b/tests/generate/test_generate_client.test_pipx_missing.approved.txt @@ -0,0 +1,5 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'poetry show algokit-client-generator --tree' in '{current_working_directory}' +DEBUG: poetry: STDOUT +DEBUG: poetry: STDERR +Error: Unable to find pipx install so that the `algokit-client-generator` can be run; please install pipx via https://pypa.github.io/pipx/ and then try `algokit generate client ...` again. diff --git a/tests/generate/test_generate_client.test_python_generator_is_installed_globally.approved.txt b/tests/generate/test_generate_client.test_python_generator_is_installed_globally.approved.txt new file mode 100644 index 00000000..13318dab --- /dev/null +++ b/tests/generate/test_generate_client.test_python_generator_is_installed_globally.approved.txt @@ -0,0 +1,18 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'poetry show algokit-client-generator --tree' in '{current_working_directory}' +DEBUG: poetry: STDOUT +DEBUG: poetry: STDERR +DEBUG: Running 'pipx --version' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'pipx list --short' in '{current_working_directory}' +DEBUG: pipx: algokit 1.13.0 +DEBUG: pipx: poetry 1.6.1 +DEBUG: pipx: algokit-client-generator 1.1.2 +Generating Python client code for application specified in {current_working_directory}/application.json and writing to client.py +DEBUG: Running 'algokitgen-py -a {current_working_directory}/application.json -o client.py' in '{current_working_directory}' +DEBUG: algokitgen-py: STDOUT +DEBUG: algokitgen-py: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_python_generator_is_installed_in_project.approved.txt b/tests/generate/test_generate_client.test_python_generator_is_installed_in_project.approved.txt new file mode 100644 index 00000000..6193cbe6 --- /dev/null +++ b/tests/generate/test_generate_client.test_python_generator_is_installed_in_project.approved.txt @@ -0,0 +1,10 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'poetry show algokit-client-generator --tree' in '{current_working_directory}' +DEBUG: poetry: algokit-client-generator 1.1.2 Algorand typed client Generator +DEBUG: poetry: └── algokit-utils 2.2.1 +Generating Python client code for application specified in {current_working_directory}/application.json and writing to client.py +DEBUG: Running 'poetry run algokitgen-py -a {current_working_directory}/application.json -o client.py' in '{current_working_directory}' +DEBUG: poetry: STDOUT +DEBUG: poetry: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_python_generator_version_is_not_installed_anywhere.approved.txt b/tests/generate/test_generate_client.test_python_generator_version_is_not_installed_anywhere.approved.txt new file mode 100644 index 00000000..c5d101d5 --- /dev/null +++ b/tests/generate/test_generate_client.test_python_generator_version_is_not_installed_anywhere.approved.txt @@ -0,0 +1,19 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'poetry show algokit-client-generator --tree' in '{current_working_directory}' +DEBUG: poetry: algokit-client-generator 1.1.2 Algorand typed client Generator +DEBUG: poetry: └── algokit-utils 2.2.1 +DEBUG: Running 'pipx --version' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'pipx list --short' in '{current_working_directory}' +DEBUG: pipx: algokit 1.13.0 +DEBUG: pipx: poetry 1.6.1 +DEBUG: pipx: algokit-client-generator 1.1.2 +DEBUG: No matching installed client generator found, run client generator via pipx +Generating Python client code for application specified in {current_working_directory}/application.json and writing to client.py +DEBUG: Running 'pipx run --spec=algokit-client-generator==1.2.0 algokitgen-py -a {current_working_directory}/application.json -o client.py' in '{current_working_directory}' +DEBUG: pipx: STDOUT +DEBUG: pipx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_typescript_generator_is_installed_globally[linux].approved.txt b/tests/generate/test_generate_client.test_typescript_generator_is_installed_globally[linux].approved.txt new file mode 100644 index 00000000..c4c3c665 --- /dev/null +++ b/tests/generate/test_generate_client.test_typescript_generator_is_installed_globally[linux].approved.txt @@ -0,0 +1,15 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm --global ls' in '{current_working_directory}' +DEBUG: npm: /Users/user/.nvm/versions/node/v20.11.0/lib +DEBUG: npm: ├── test@1.2.3 +DEBUG: npm: └── @algorandfoundation/algokit-client-generator@1.1.2 +Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to client.py +DEBUG: Running 'npx @algorandfoundation/algokit-client-generator generate -a {current_working_directory}/application.json -o client.py' in '{current_working_directory}' +DEBUG: npx: STDOUT +DEBUG: npx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_typescript_generator_is_installed_globally[macOS].approved.txt b/tests/generate/test_generate_client.test_typescript_generator_is_installed_globally[macOS].approved.txt new file mode 100644 index 00000000..c4c3c665 --- /dev/null +++ b/tests/generate/test_generate_client.test_typescript_generator_is_installed_globally[macOS].approved.txt @@ -0,0 +1,15 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm ls' in '{current_working_directory}' +DEBUG: npm: STDOUT +DEBUG: npm: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm --global ls' in '{current_working_directory}' +DEBUG: npm: /Users/user/.nvm/versions/node/v20.11.0/lib +DEBUG: npm: ├── test@1.2.3 +DEBUG: npm: └── @algorandfoundation/algokit-client-generator@1.1.2 +Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to client.py +DEBUG: Running 'npx @algorandfoundation/algokit-client-generator generate -a {current_working_directory}/application.json -o client.py' in '{current_working_directory}' +DEBUG: npx: STDOUT +DEBUG: npx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_typescript_generator_is_installed_globally[windows].approved.txt b/tests/generate/test_generate_client.test_typescript_generator_is_installed_globally[windows].approved.txt new file mode 100644 index 00000000..f8246c91 --- /dev/null +++ b/tests/generate/test_generate_client.test_typescript_generator_is_installed_globally[windows].approved.txt @@ -0,0 +1,15 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm.cmd ls' in '{current_working_directory}' +DEBUG: npm.cmd: STDOUT +DEBUG: npm.cmd: STDERR +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm.cmd --global ls' in '{current_working_directory}' +DEBUG: npm.cmd: /Users/user/.nvm/versions/node/v20.11.0/lib +DEBUG: npm.cmd: ├── test@1.2.3 +DEBUG: npm.cmd: └── @algorandfoundation/algokit-client-generator@1.1.2 +Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to client.py +DEBUG: Running 'npx.cmd @algorandfoundation/algokit-client-generator generate -a {current_working_directory}/application.json -o client.py' in '{current_working_directory}' +DEBUG: npx.cmd: STDOUT +DEBUG: npx.cmd: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_typescript_generator_is_installed_in_project[linux].approved.txt b/tests/generate/test_generate_client.test_typescript_generator_is_installed_in_project[linux].approved.txt new file mode 100644 index 00000000..3b5f3d5f --- /dev/null +++ b/tests/generate/test_generate_client.test_typescript_generator_is_installed_in_project[linux].approved.txt @@ -0,0 +1,11 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm ls' in '{current_working_directory}' +DEBUG: npm: /Users/user/my-project +DEBUG: npm: ├── test@1.2.3 +DEBUG: npm: └── @algorandfoundation/algokit-client-generator@1.1.2 +Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to client.py +DEBUG: Running 'npx @algorandfoundation/algokit-client-generator generate -a {current_working_directory}/application.json -o client.py' in '{current_working_directory}' +DEBUG: npx: STDOUT +DEBUG: npx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_typescript_generator_is_installed_in_project[macOS].approved.txt b/tests/generate/test_generate_client.test_typescript_generator_is_installed_in_project[macOS].approved.txt new file mode 100644 index 00000000..3b5f3d5f --- /dev/null +++ b/tests/generate/test_generate_client.test_typescript_generator_is_installed_in_project[macOS].approved.txt @@ -0,0 +1,11 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm ls' in '{current_working_directory}' +DEBUG: npm: /Users/user/my-project +DEBUG: npm: ├── test@1.2.3 +DEBUG: npm: └── @algorandfoundation/algokit-client-generator@1.1.2 +Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to client.py +DEBUG: Running 'npx @algorandfoundation/algokit-client-generator generate -a {current_working_directory}/application.json -o client.py' in '{current_working_directory}' +DEBUG: npx: STDOUT +DEBUG: npx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_typescript_generator_is_installed_in_project[windows].approved.txt b/tests/generate/test_generate_client.test_typescript_generator_is_installed_in_project[windows].approved.txt new file mode 100644 index 00000000..de67a1b6 --- /dev/null +++ b/tests/generate/test_generate_client.test_typescript_generator_is_installed_in_project[windows].approved.txt @@ -0,0 +1,11 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm.cmd ls' in '{current_working_directory}' +DEBUG: npm.cmd: /Users/user/my-project +DEBUG: npm.cmd: ├── test@1.2.3 +DEBUG: npm.cmd: └── @algorandfoundation/algokit-client-generator@1.1.2 +Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to client.py +DEBUG: Running 'npx.cmd @algorandfoundation/algokit-client-generator generate -a {current_working_directory}/application.json -o client.py' in '{current_working_directory}' +DEBUG: npx.cmd: STDOUT +DEBUG: npx.cmd: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_typescript_generator_version_is_not_installed_anywhere[linux].approved.txt b/tests/generate/test_generate_client.test_typescript_generator_version_is_not_installed_anywhere[linux].approved.txt new file mode 100644 index 00000000..eb09aee6 --- /dev/null +++ b/tests/generate/test_generate_client.test_typescript_generator_version_is_not_installed_anywhere[linux].approved.txt @@ -0,0 +1,17 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm ls' in '{current_working_directory}' +DEBUG: npm: /Users/user/my-project +DEBUG: npm: ├── test@1.2.3 +DEBUG: npm: └── @algorandfoundation/algokit-client-generator@1.1.2 +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm --global ls' in '{current_working_directory}' +DEBUG: npm: /Users/user/.nvm/versions/node/v20.11.0/lib +DEBUG: npm: ├── test@1.2.3 +DEBUG: npm: └── @algorandfoundation/algokit-client-generator@1.1.2 +DEBUG: No matching installed client generator found, run client generator via npx +Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to client.py +DEBUG: Running 'npx --yes @algorandfoundation/algokit-client-generator@1.2.0 generate -a {current_working_directory}/application.json -o client.py' in '{current_working_directory}' +DEBUG: npx: STDOUT +DEBUG: npx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_typescript_generator_version_is_not_installed_anywhere[macOS].approved.txt b/tests/generate/test_generate_client.test_typescript_generator_version_is_not_installed_anywhere[macOS].approved.txt new file mode 100644 index 00000000..eb09aee6 --- /dev/null +++ b/tests/generate/test_generate_client.test_typescript_generator_version_is_not_installed_anywhere[macOS].approved.txt @@ -0,0 +1,17 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm ls' in '{current_working_directory}' +DEBUG: npm: /Users/user/my-project +DEBUG: npm: ├── test@1.2.3 +DEBUG: npm: └── @algorandfoundation/algokit-client-generator@1.1.2 +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm --global ls' in '{current_working_directory}' +DEBUG: npm: /Users/user/.nvm/versions/node/v20.11.0/lib +DEBUG: npm: ├── test@1.2.3 +DEBUG: npm: └── @algorandfoundation/algokit-client-generator@1.1.2 +DEBUG: No matching installed client generator found, run client generator via npx +Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to client.py +DEBUG: Running 'npx --yes @algorandfoundation/algokit-client-generator@1.2.0 generate -a {current_working_directory}/application.json -o client.py' in '{current_working_directory}' +DEBUG: npx: STDOUT +DEBUG: npx: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_client.test_typescript_generator_version_is_not_installed_anywhere[windows].approved.txt b/tests/generate/test_generate_client.test_typescript_generator_version_is_not_installed_anywhere[windows].approved.txt new file mode 100644 index 00000000..b2161678 --- /dev/null +++ b/tests/generate/test_generate_client.test_typescript_generator_version_is_not_installed_anywhere[windows].approved.txt @@ -0,0 +1,17 @@ +DEBUG: Searching for project installed client generator +DEBUG: Running 'npm.cmd ls' in '{current_working_directory}' +DEBUG: npm.cmd: /Users/user/my-project +DEBUG: npm.cmd: ├── test@1.2.3 +DEBUG: npm.cmd: └── @algorandfoundation/algokit-client-generator@1.1.2 +DEBUG: Searching for globally installed client generator +DEBUG: Running 'npm.cmd --global ls' in '{current_working_directory}' +DEBUG: npm.cmd: /Users/user/.nvm/versions/node/v20.11.0/lib +DEBUG: npm.cmd: ├── test@1.2.3 +DEBUG: npm.cmd: └── @algorandfoundation/algokit-client-generator@1.1.2 +DEBUG: No matching installed client generator found, run client generator via npx +Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to client.py +DEBUG: Running 'npx.cmd --yes @algorandfoundation/algokit-client-generator@1.2.0 generate -a {current_working_directory}/application.json -o client.py' in '{current_working_directory}' +DEBUG: npx.cmd: STDOUT +DEBUG: npx.cmd: STDERR +STDOUT +STDERR diff --git a/tests/generate/test_generate_custom_generate_commands.test_generate_custom_generate_commands_valid_generator.approved.txt b/tests/generate/test_generate_custom_generate_commands.test_generate_custom_generate_commands_valid_generator.approved.txt index a4fbaeaa..ca88bb99 100644 --- a/tests/generate/test_generate_custom_generate_commands.test_generate_custom_generate_commands_valid_generator.approved.txt +++ b/tests/generate/test_generate_custom_generate_commands.test_generate_custom_generate_commands_valid_generator.approved.txt @@ -1,5 +1,4 @@ DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml -DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml Usage: algokit generate [OPTIONS] COMMAND [ARGS]... Generate code for an Algorand project. diff --git a/tests/generate/test_generate_custom_generate_commands.test_generate_custom_generate_commands_valid_generator_no_description.approved.txt b/tests/generate/test_generate_custom_generate_commands.test_generate_custom_generate_commands_valid_generator_no_description.approved.txt index 172e9ed6..91839b13 100644 --- a/tests/generate/test_generate_custom_generate_commands.test_generate_custom_generate_commands_valid_generator_no_description.approved.txt +++ b/tests/generate/test_generate_custom_generate_commands.test_generate_custom_generate_commands_valid_generator_no_description.approved.txt @@ -1,5 +1,4 @@ DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml -DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml Usage: algokit generate [OPTIONS] COMMAND [ARGS]... Generate code for an Algorand project. diff --git a/tests/init/test_init.py b/tests/init/test_init.py index f006630a..0f287051 100644 --- a/tests/init/test_init.py +++ b/tests/init/test_init.py @@ -1,3 +1,4 @@ +import json import subprocess from collections.abc import Callable from dataclasses import dataclass @@ -23,6 +24,23 @@ GIT_BUNDLE_PATH = PARENT_DIRECTORY / "copier-helloworld.bundle" +def _remove_git_hints(output: str) -> str: + git_init_hint_prefix = "DEBUG: git: hint:" + lines = [line for line in output.splitlines() if not line.startswith(git_init_hint_prefix)] + return "\n".join(lines) + + +def _remove_project_paths(output: str) -> str: + lines = [ + "DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml" + if "DEBUG: Attempting to load project config from " in line + else line + for line in output.splitlines() + ] + + return "\n".join(lines) + + class MockPipeInput(str, Enum): LEFT = "\x1b[D" RIGHT = "\x1b[C" @@ -44,6 +62,7 @@ class MockQuestionaryAnswer: def make_output_scrubber(*extra_scrubbers: Callable[[str], str], **extra_tokens: str) -> Scrubber: default_tokens = {"test_parent_directory": str(PARENT_DIRECTORY)} + tokens = default_tokens | extra_tokens return combine_scrubbers( *extra_scrubbers, @@ -51,6 +70,7 @@ def make_output_scrubber(*extra_scrubbers: Callable[[str], str], **extra_tokens: TokenScrubber(tokens=tokens), TokenScrubber(tokens={"test_parent_directory": str(PARENT_DIRECTORY).replace("\\", "/")}), lambda t: t.replace("{test_parent_directory}\\", "{test_parent_directory}/"), + _remove_project_paths, ) @@ -65,7 +85,7 @@ def which_mock(mocker: MockerFixture) -> WhichMock: class ExtendedTemplateKey(str, Enum): # Include all keys from TemplateKey and add new ones BASE = "base" - PUYA = "puya" + PYTHON = "python" TEALSCRIPT = "tealscript" FULLSTACK = "fullstack" REACT = "react" @@ -88,7 +108,7 @@ def _set_blessed_templates(mocker: MockerFixture) -> None: blessed_templates = { ExtendedTemplateKey.SIMPLE: BlessedTemplateSource( url="gh:robdmoore/copier-helloworld", - description="Does nothing helpful.", + description="Does nothing helpful. simple", ), ExtendedTemplateKey.BEAKER: BlessedTemplateSource( url="gh:algorandfoundation/algokit-beaker-default-template", @@ -101,19 +121,19 @@ def _set_blessed_templates(mocker: MockerFixture) -> None: ), ExtendedTemplateKey.FULLSTACK: BlessedTemplateSource( url="gh:robdmoore/copier-helloworld", - description="Does nothing helpful.", + description="Does nothing helpful. fullstack", ), - ExtendedTemplateKey.PUYA: BlessedTemplateSource( + ExtendedTemplateKey.PYTHON: BlessedTemplateSource( url="gh:robdmoore/copier-helloworld", - description="Does nothing helpful.", + description="Does nothing helpful. python", ), ExtendedTemplateKey.REACT: BlessedTemplateSource( url="gh:robdmoore/copier-helloworld", - description="Does nothing helpful.", + description="Does nothing helpful. react", ), ExtendedTemplateKey.BASE: BlessedTemplateSource( url="gh:algorandfoundation/algokit-base-template", - description="Does nothing helpful.", + description="Does nothing helpful. base", ), } @@ -844,7 +864,185 @@ def test_init_wizard_v2_flow( ) -def _remove_git_hints(output: str) -> str: - git_init_hint_prefix = "DEBUG: git: hint:" - lines = [line for line in output.splitlines() if not line.startswith(git_init_hint_prefix)] - return "\n".join(lines) +def test_init_wizard_v2_workspace_nesting( + tmp_path_factory: TempPathFactory, +) -> None: + # Arrange + cwd = tmp_path_factory.mktemp("cwd") + + # Act + project_a_result = invoke( + "init -t python --no-git --defaults --name myapp " + "--UNSAFE-SECURITY-accept-template-url -a preset_name 'production'", + cwd=cwd, + ) + project_b_result = invoke( + "init -t python --no-git --defaults --name myapp2 " + "--UNSAFE-SECURITY-accept-template-url -a preset_name 'starter'", + cwd=cwd / "myapp" / "projects", + ) + project_c_result = invoke( + "init -t python --no-git --defaults --name myapp3 " + "--UNSAFE-SECURITY-accept-template-url -a preset_name 'starter' --no-workspace", + cwd=cwd / "myapp" / "projects", + ) + project_d_result = invoke( + "init -t python --no-git --defaults --name myapp4 " + "--UNSAFE-SECURITY-accept-template-url -a preset_name 'starter'", + cwd=cwd / "myapp", + ) + + # Assert + assert project_a_result.exit_code == 0 + assert project_b_result.exit_code == 1 + assert project_c_result.exit_code == 0 + assert project_d_result.exit_code == 0 + + +def test_init_wizard_v2_github_folder_with_workspace( + tmp_path_factory: TempPathFactory, mock_questionary_input: PipeInput +) -> None: + # Arrange + cwd = tmp_path_factory.mktemp("cwd") + answer = MockQuestionaryAnswer("Smart Contract", [MockPipeInput.ENTER, MockPipeInput.ENTER]) + for command in answer.commands: + mock_questionary_input.send_text(command.value) + + # Act + result = invoke( + "init -t beaker --no-git --defaults --name myapp " + "--UNSAFE-SECURITY-accept-template-url -a preset_name 'production'", + cwd=cwd, + ) + + # Assert + cwd /= "myapp" + assert result.exit_code == 0 + assert not cwd.joinpath("projects/myapp/.github").exists() + assert cwd.joinpath(".github").exists() + assert cwd.glob(".github/workflows/*.yaml") + + +def test_init_wizard_v2_github_folder_with_workspace_partial( + tmp_path_factory: TempPathFactory, mock_questionary_input: PipeInput +) -> None: + # Arrange + cwd = tmp_path_factory.mktemp("cwd") + mock_questionary_input.send_text("y\n") # Simulate workspace selection + + github_workflow_path = cwd / "myapp" / ".github" / "workflows" + github_workflow_path.mkdir(parents=True, exist_ok=True) + (github_workflow_path / "cd.yaml").touch() + + # Act + result = invoke( + "init -t beaker --no-git --defaults --name myapp " + "--UNSAFE-SECURITY-accept-template-url -a preset_name 'production'", + input="y\n", + cwd=cwd, + ) + + # Assert + cwd /= "myapp" + assert result.exit_code == 0 + assert (cwd / "projects/myapp/.github/workflows/cd.yaml").read_text() != "" + assert (cwd / ".github/workflows/cd.yaml").read_text() == "" + assert cwd.glob(".github/workflows/*.yaml") + assert len(list(cwd.glob("projects/myapp/.github/workflows/*.yaml"))) == 1 + + +def test_init_wizard_v2_github_folder_no_workspace( + tmp_path_factory: TempPathFactory, mock_questionary_input: PipeInput +) -> None: + # Arrange + cwd = tmp_path_factory.mktemp("cwd") + answer = MockQuestionaryAnswer("Smart Contract", [MockPipeInput.ENTER, MockPipeInput.ENTER]) + for command in answer.commands: + mock_questionary_input.send_text(command.value) + + # Act + result = invoke( + "init -t beaker --no-git --defaults --name myapp " + "--UNSAFE-SECURITY-accept-template-url -a preset_name 'production' --no-workspace", + cwd=cwd, + ) + + # Assert + cwd /= "myapp" + assert result.exit_code == 0 + assert not cwd.joinpath("projects").exists() + assert cwd.joinpath(".github").exists() + assert cwd.glob(".github/workflows/*.yaml") + + +@pytest.mark.parametrize( + ("workspace_content", "expected_path", "expect_warning"), + [ + # Scenario 1: Valid codespace, new project added + ( + """ + { + "folders": [ + { + "path": ".", + "name": "ROOT" + }, + { + "path": "projects/myapp" + } + ] + } + """, + "projects/myapp2", + False, + ), + # Scenario 2: No codespace, nothing happens + (None, None, False), + # Scenario 3: Invalid codespace, warning expected + ("INVALID_JSON", None, True), + ], +) +def test_init_wizard_v2_append_to_vscode_workspace( + *, + which_mock: WhichMock, + proc_mock: ProcMock, + tmp_path_factory: TempPathFactory, + mock_questionary_input: PipeInput, + workspace_content: str, + expected_path: str, + expect_warning: bool, +) -> None: + # Arrange + code_cmd = which_mock.add("code") + proc_mock.set_output([code_cmd], ["Launch project"]) + + cwd = tmp_path_factory.mktemp("cwd") + answer = MockQuestionaryAnswer("Smart Contract", [MockPipeInput.ENTER, MockPipeInput.ENTER]) + for command in answer.commands: + mock_questionary_input.send_text(command.value) + + # Act + project_a_result = invoke( + "init -t beaker --no-git --defaults --name myapp " + "--UNSAFE-SECURITY-accept-template-url -a preset_name 'production'", + cwd=cwd, + ) + if workspace_content is not None: + workspace_file = cwd / "myapp" / "myapp.code-workspace" + workspace_file.write_text(workspace_content) + + project_b_result = invoke( + "init -t beaker --no-git --defaults --name myapp2 " + "--UNSAFE-SECURITY-accept-template-url -a preset_name 'starter'", + cwd=cwd / "myapp", + ) + + # Assert + assert project_a_result.exit_code == 0 + assert project_b_result.exit_code == 0 + if expected_path and "workspace_file" in locals(): + workspace_data = json.loads(workspace_file.read_text()) + assert workspace_data["folders"][-1]["path"] == expected_path + if expect_warning: + # This assumes the existence of a function `verify` to check for warnings in the output + verify(project_b_result.output) diff --git a/tests/init/test_init.test_init_ask_about_git.approved.txt b/tests/init/test_init.test_init_ask_about_git.approved.txt index 8f0b0078..bf3ffe94 100644 --- a/tests/init/test_init.test_init_ask_about_git.approved.txt +++ b/tests/init/test_init.test_init_ask_about_git.approved.txt @@ -4,6 +4,12 @@ WARNING: Community templates have not been reviewed, and can execute arbitrary c Please inspect the template repository, and pay particular attention to the values of _tasks, _migrations and _jinja_extensions in copier.yml ? Continue anyway? (y/N) DEBUG: template source = {test_parent_directory}/copier-helloworld.bundle +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. @@ -11,9 +17,16 @@ Starting template copy and render at {current_working_directory}/myapp... No git tags found in template; using HEAD as ref DEBUG: final clone URL = {test_parent_directory}/copier-helloworld.bundle Template render complete! -DEBUG: Attempting to load project config from {current_working_directory}/myapp/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. Executed `algokit bootstrap all` in {current_working_directory}/myapp +🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 DEBUG: Running 'git rev-parse --show-toplevel' in '{current_working_directory}/myapp' DEBUG: git: fatal: not a git repository (or any of the parent directories): .git ? Would you like to initialise a git repository and perform an initial commit? (Y/n) @@ -27,4 +40,3 @@ DEBUG: git: [main (root-commit) {git_initial_commit_hash}] Project initialised w DEBUG: git: 1 file changed, 1 insertion(+) DEBUG: git: create mode 100644 myapp/helloworld.txt 🎉 Performed initial git commit successfully! 🎉 -🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 diff --git a/tests/init/test_init.test_init_bootstrap_no.approved.txt b/tests/init/test_init.test_init_bootstrap_no.approved.txt index 2a2a9279..d28fb22e 100644 --- a/tests/init/test_init.test_init_bootstrap_no.approved.txt +++ b/tests/init/test_init.test_init_bootstrap_no.approved.txt @@ -3,6 +3,12 @@ DEBUG: No .algokit.toml file found in the project directory. WARNING: Community templates have not been reviewed, and can execute arbitrary code. Please inspect the template repository, and pay particular attention to the values of _tasks, _migrations and _jinja_extensions in copier.yml DEBUG: template source = {test_parent_directory}/copier-helloworld.bundle +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. @@ -10,5 +16,11 @@ Starting template copy and render at {current_working_directory}/myapp... DEBUG: final clone URL = {test_parent_directory}/copier-helloworld.bundle No git tags found in template; using HEAD as ref Template render complete! +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. ? Do you want to run `algokit bootstrap` for this new project? This will install and configure dependencies allowing it to be run immediately. (Y/n) 🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 diff --git a/tests/init/test_init.test_init_bootstrap_yes.approved.txt b/tests/init/test_init.test_init_bootstrap_yes.approved.txt index 396afe85..5d5950d6 100644 --- a/tests/init/test_init.test_init_bootstrap_yes.approved.txt +++ b/tests/init/test_init.test_init_bootstrap_yes.approved.txt @@ -3,6 +3,12 @@ DEBUG: No .algokit.toml file found in the project directory. WARNING: Community templates have not been reviewed, and can execute arbitrary code. Please inspect the template repository, and pay particular attention to the values of _tasks, _migrations and _jinja_extensions in copier.yml DEBUG: template source = {test_parent_directory}/copier-helloworld.bundle +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. @@ -10,8 +16,14 @@ Starting template copy and render at {current_working_directory}/myapp... DEBUG: final clone URL = {test_parent_directory}/copier-helloworld.bundle No git tags found in template; using HEAD as ref Template render complete! +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. ? Do you want to run `algokit bootstrap` for this new project? This will install and configure dependencies allowing it to be run immediately. (Y/n) -DEBUG: Attempting to load project config from {current_working_directory}/myapp/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. Executed `algokit bootstrap all` in {current_working_directory}/myapp 🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 diff --git a/tests/init/test_init.test_init_help.approved.txt b/tests/init/test_init.test_init_help.approved.txt index de08e8b1..19b80104 100644 --- a/tests/init/test_init.test_init_help.approved.txt +++ b/tests/init/test_init.test_init_help.approved.txt @@ -22,7 +22,7 @@ Usage: algokit init [OPTIONS] Options: -n, --name TEXT Name of the project / directory / repository to create. - -t, --template [simple|beaker|beaker_with_version|fullstack|puya|react|base] + -t, --template [simple|beaker|beaker_with_version|fullstack|python|react|base] Name of an official template to use. To choose interactively, run this command with no arguments. @@ -47,7 +47,10 @@ Options: IDE config are detected. Supported IDEs: VS Code. --workspace / --no-workspace Whether to prefer structuring standalone - projects as part of a workspace. + projects as part of a workspace. An AlgoKit + workspace is a conventional project structure + that allows managing multiple standalone + projects in a monorepo. -a, --answer Answers key/value pairs to pass to the template. -h, --help Show this message and exit. diff --git a/tests/init/test_init.test_init_input_template_url.approved.txt b/tests/init/test_init.test_init_input_template_url.approved.txt index 334f13af..cdd4d9e4 100644 --- a/tests/init/test_init.test_init_input_template_url.approved.txt +++ b/tests/init/test_init.test_init_input_template_url.approved.txt @@ -22,8 +22,14 @@ Valid examples: - ~/path/to/git/repo - ~/path/to/git/repo.bundle -? Custom template URL: +? Custom template URL: DEBUG: template source = {test_parent_directory}/copier-helloworld.bundle +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. @@ -31,7 +37,13 @@ Starting template copy and render at {current_working_directory}/myapp... No git tags found in template; using HEAD as ref DEBUG: final clone URL = {test_parent_directory}/copier-helloworld.bundle Template render complete! -DEBUG: Attempting to load project config from {current_working_directory}/myapp/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. Executed `algokit bootstrap all` in {current_working_directory}/myapp 🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 diff --git a/tests/init/test_init.test_init_minimal_interaction_required_no_git_no_network_no_bootstrap.approved.txt b/tests/init/test_init.test_init_minimal_interaction_required_no_git_no_network_no_bootstrap.approved.txt index 3b32dbd6..7786e953 100644 --- a/tests/init/test_init.test_init_minimal_interaction_required_no_git_no_network_no_bootstrap.approved.txt +++ b/tests/init/test_init.test_init_minimal_interaction_required_no_git_no_network_no_bootstrap.approved.txt @@ -4,6 +4,12 @@ WARNING: Community templates have not been reviewed, and can execute arbitrary c Please inspect the template repository, and pay particular attention to the values of _tasks, _migrations and _jinja_extensions in copier.yml ? Continue anyway? (y/N) DEBUG: template source = {test_parent_directory}/copier-helloworld.bundle +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. @@ -11,4 +17,10 @@ Starting template copy and render at {current_working_directory}/myapp... No git tags found in template; using HEAD as ref DEBUG: final clone URL = {test_parent_directory}/copier-helloworld.bundle Template render complete! +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. 🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 diff --git a/tests/init/test_init.test_init_minimal_interaction_required_yes_git_no_network.approved.txt b/tests/init/test_init.test_init_minimal_interaction_required_yes_git_no_network.approved.txt index 61d10b46..219ef668 100644 --- a/tests/init/test_init.test_init_minimal_interaction_required_yes_git_no_network.approved.txt +++ b/tests/init/test_init.test_init_minimal_interaction_required_yes_git_no_network.approved.txt @@ -4,6 +4,12 @@ WARNING: Community templates have not been reviewed, and can execute arbitrary c Please inspect the template repository, and pay particular attention to the values of _tasks, _migrations and _jinja_extensions in copier.yml ? Continue anyway? (y/N) DEBUG: template source = {test_parent_directory}/copier-helloworld.bundle +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. @@ -11,9 +17,16 @@ Starting template copy and render at {current_working_directory}/myapp... No git tags found in template; using HEAD as ref DEBUG: final clone URL = {test_parent_directory}/copier-helloworld.bundle Template render complete! -DEBUG: Attempting to load project config from {current_working_directory}/myapp/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. Executed `algokit bootstrap all` in {current_working_directory}/myapp +🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 DEBUG: Running 'git rev-parse --show-toplevel' in '{current_working_directory}/myapp' DEBUG: git: fatal: not a git repository (or any of the parent directories): .git DEBUG: Running 'git init' in '{current_working_directory}/myapp' @@ -26,4 +39,3 @@ DEBUG: git: [main (root-commit) {git_initial_commit_hash}] Project initialised w DEBUG: git: 1 file changed, 1 insertion(+) DEBUG: git: create mode 100644 myapp/helloworld.txt 🎉 Performed initial git commit successfully! 🎉 -🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 diff --git a/tests/init/test_init.test_init_no_interaction_required_defaults_no_git_no_network.approved.txt b/tests/init/test_init.test_init_no_interaction_required_defaults_no_git_no_network.approved.txt index 99c33360..369c887a 100644 --- a/tests/init/test_init.test_init_no_interaction_required_defaults_no_git_no_network.approved.txt +++ b/tests/init/test_init.test_init_no_interaction_required_defaults_no_git_no_network.approved.txt @@ -3,6 +3,12 @@ DEBUG: No .algokit.toml file found in the project directory. WARNING: Community templates have not been reviewed, and can execute arbitrary code. Please inspect the template repository, and pay particular attention to the values of _tasks, _migrations and _jinja_extensions in copier.yml DEBUG: template source = {test_parent_directory}/copier-helloworld.bundle +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. @@ -10,7 +16,13 @@ Starting template copy and render at {current_working_directory}/myapp... No git tags found in template; using HEAD as ref DEBUG: final clone URL = {test_parent_directory}/copier-helloworld.bundle Template render complete! -DEBUG: Attempting to load project config from {current_working_directory}/myapp/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. Executed `algokit bootstrap all` in {current_working_directory}/myapp 🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 diff --git a/tests/init/test_init.test_init_no_interaction_required_no_git_no_network.approved.txt b/tests/init/test_init.test_init_no_interaction_required_no_git_no_network.approved.txt index afe63737..fc60c373 100644 --- a/tests/init/test_init.test_init_no_interaction_required_no_git_no_network.approved.txt +++ b/tests/init/test_init.test_init_no_interaction_required_no_git_no_network.approved.txt @@ -3,6 +3,12 @@ DEBUG: No .algokit.toml file found in the project directory. WARNING: Community templates have not been reviewed, and can execute arbitrary code. Please inspect the template repository, and pay particular attention to the values of _tasks, _migrations and _jinja_extensions in copier.yml DEBUG: template source = {test_parent_directory}/copier-helloworld.bundle +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. @@ -10,7 +16,13 @@ Starting template copy and render at {current_working_directory}/myapp... DEBUG: final clone URL = {test_parent_directory}/copier-helloworld.bundle No git tags found in template; using HEAD as ref Template render complete! -DEBUG: Attempting to load project config from {current_working_directory}/myapp/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. Executed `algokit bootstrap all` in {current_working_directory}/myapp 🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 diff --git a/tests/init/test_init.test_init_no_interaction_required_no_git_no_network_with_no_ide.approved.txt b/tests/init/test_init.test_init_no_interaction_required_no_git_no_network_with_no_ide.approved.txt index b5a4e54c..37bc35d2 100644 --- a/tests/init/test_init.test_init_no_interaction_required_no_git_no_network_with_no_ide.approved.txt +++ b/tests/init/test_init.test_init_no_interaction_required_no_git_no_network_with_no_ide.approved.txt @@ -5,6 +5,12 @@ Please inspect the template repository, and pay particular attention to the valu DEBUG: template source = {test_parent_directory}/copier-helloworld.bundle WARNING: Re-using existing directory, this is not recommended because if project generation fails, then we can't automatically cleanup. ? Continue anyway? (y/N) +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. @@ -12,7 +18,13 @@ Starting template copy and render at {current_working_directory}/myapp... DEBUG: final clone URL = {test_parent_directory}/copier-helloworld.bundle No git tags found in template; using HEAD as ref Template render complete! -DEBUG: Attempting to load project config from {current_working_directory}/myapp/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. Executed `algokit bootstrap all` in {current_working_directory}/myapp 🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 diff --git a/tests/init/test_init.test_init_no_interaction_required_no_git_no_network_with_vscode.approved.txt b/tests/init/test_init.test_init_no_interaction_required_no_git_no_network_with_vscode.approved.txt index 181baa4d..c2396929 100644 --- a/tests/init/test_init.test_init_no_interaction_required_no_git_no_network_with_vscode.approved.txt +++ b/tests/init/test_init.test_init_no_interaction_required_no_git_no_network_with_vscode.approved.txt @@ -5,6 +5,12 @@ Please inspect the template repository, and pay particular attention to the valu DEBUG: template source = {test_parent_directory}/copier-helloworld.bundle WARNING: Re-using existing directory, this is not recommended because if project generation fails, then we can't automatically cleanup. ? Continue anyway? (y/N) +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. @@ -12,7 +18,13 @@ Starting template copy and render at {current_working_directory}/myapp... DEBUG: final clone URL = {test_parent_directory}/copier-helloworld.bundle No git tags found in template; using HEAD as ref Template render complete! -DEBUG: Attempting to load project config from {current_working_directory}/myapp/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. Executed `algokit bootstrap all` in {current_working_directory}/myapp 🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 diff --git a/tests/init/test_init.test_init_no_interaction_required_no_git_no_network_with_vscode_and_readme.approved.txt b/tests/init/test_init.test_init_no_interaction_required_no_git_no_network_with_vscode_and_readme.approved.txt index 7ac48e5a..9bab6b3b 100644 --- a/tests/init/test_init.test_init_no_interaction_required_no_git_no_network_with_vscode_and_readme.approved.txt +++ b/tests/init/test_init.test_init_no_interaction_required_no_git_no_network_with_vscode_and_readme.approved.txt @@ -5,6 +5,12 @@ Please inspect the template repository, and pay particular attention to the valu DEBUG: template source = {test_parent_directory}/copier-helloworld.bundle WARNING: Re-using existing directory, this is not recommended because if project generation fails, then we can't automatically cleanup. ? Continue anyway? (y/N) +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. @@ -12,7 +18,13 @@ Starting template copy and render at {current_working_directory}/myapp... DEBUG: final clone URL = {test_parent_directory}/copier-helloworld.bundle No git tags found in template; using HEAD as ref Template render complete! -DEBUG: Attempting to load project config from {current_working_directory}/myapp/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. Executed `algokit bootstrap all` in {current_working_directory}/myapp 🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 diff --git a/tests/init/test_init.test_init_project_name.approved.txt b/tests/init/test_init.test_init_project_name.approved.txt index b733eb03..b5cc9190 100644 --- a/tests/init/test_init.test_init_project_name.approved.txt +++ b/tests/init/test_init.test_init_project_name.approved.txt @@ -1,7 +1,13 @@ WARNING: Community templates have not been reviewed, and can execute arbitrary code. Please inspect the template repository, and pay particular attention to the values of _tasks, _migrations and _jinja_extensions in copier.yml DEBUG: template source = {test_parent_directory}/copier-helloworld.bundle -? Name of project / directory to create the project in: +? Name of project / directory to create the project in: +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/FAKE_PROJECT @@ -11,7 +17,13 @@ Starting template copy and render at {current_working_directory}/FAKE_PROJECT... No git tags found in template; using HEAD as ref DEBUG: final clone URL = {test_parent_directory}/copier-helloworld.bundle Template render complete! -DEBUG: Attempting to load project config from {current_working_directory}/FAKE_PROJECT/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. Executed `algokit bootstrap all` in {current_working_directory}/FAKE_PROJECT 🙌 Project initialized at `FAKE_PROJECT`! For template specific next steps, consult the documentation of your selected template 🧐 diff --git a/tests/init/test_init.test_init_project_name_not_empty.approved.txt b/tests/init/test_init.test_init_project_name_not_empty.approved.txt index b733eb03..b5cc9190 100644 --- a/tests/init/test_init.test_init_project_name_not_empty.approved.txt +++ b/tests/init/test_init.test_init_project_name_not_empty.approved.txt @@ -1,7 +1,13 @@ WARNING: Community templates have not been reviewed, and can execute arbitrary code. Please inspect the template repository, and pay particular attention to the values of _tasks, _migrations and _jinja_extensions in copier.yml DEBUG: template source = {test_parent_directory}/copier-helloworld.bundle -? Name of project / directory to create the project in: +? Name of project / directory to create the project in: +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/FAKE_PROJECT @@ -11,7 +17,13 @@ Starting template copy and render at {current_working_directory}/FAKE_PROJECT... No git tags found in template; using HEAD as ref DEBUG: final clone URL = {test_parent_directory}/copier-helloworld.bundle Template render complete! -DEBUG: Attempting to load project config from {current_working_directory}/FAKE_PROJECT/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. Executed `algokit bootstrap all` in {current_working_directory}/FAKE_PROJECT 🙌 Project initialized at `FAKE_PROJECT`! For template specific next steps, consult the documentation of your selected template 🧐 diff --git a/tests/init/test_init.test_init_project_name_reenter_folder_name.approved.txt b/tests/init/test_init.test_init_project_name_reenter_folder_name.approved.txt index c506cb3a..89e8ff81 100644 --- a/tests/init/test_init.test_init_project_name_reenter_folder_name.approved.txt +++ b/tests/init/test_init.test_init_project_name_reenter_folder_name.approved.txt @@ -1,12 +1,16 @@ WARNING: Community templates have not been reviewed, and can execute arbitrary code. Please inspect the template repository, and pay particular attention to the values of _tasks, _migrations and _jinja_extensions in copier.yml DEBUG: template source = {test_parent_directory}/copier-helloworld.bundle -? Name of project / directory to create the project in: +? Name of project / directory to create the project in: DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. WARNING: Re-using existing directory, this is not recommended because if project generation fails, then we can't automatically cleanup. ? Continue anyway? (y/N) -? Name of project / directory to create the project in: +? Name of project / directory to create the project in: +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/FAKE_PROJECT_2 @@ -16,7 +20,13 @@ Starting template copy and render at {current_working_directory}/FAKE_PROJECT_2. No git tags found in template; using HEAD as ref DEBUG: final clone URL = {test_parent_directory}/copier-helloworld.bundle Template render complete! -DEBUG: Attempting to load project config from {current_working_directory}/FAKE_PROJECT_2/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. Executed `algokit bootstrap all` in {current_working_directory}/FAKE_PROJECT_2 🙌 Project initialized at `FAKE_PROJECT_2`! For template specific next steps, consult the documentation of your selected template 🧐 diff --git a/tests/init/test_init.test_init_template_selection.approved.txt b/tests/init/test_init.test_init_template_selection.approved.txt index ef6a075c..ffd2381f 100644 --- a/tests/init/test_init.test_init_template_selection.approved.txt +++ b/tests/init/test_init.test_init_template_selection.approved.txt @@ -12,6 +12,12 @@ TypeScript 📘 PyTeal 🔧 DEBUG: selected language = ContractLanguage.PYTHON DEBUG: template source = gh:robdmoore/copier-helloworld +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. @@ -19,7 +25,13 @@ Starting template copy and render at {current_working_directory}/myapp... No git tags found in template; using HEAD as ref DEBUG: final clone URL = https://github.com/robdmoore/copier-helloworld.git Template render complete! -DEBUG: Attempting to load project config from {current_working_directory}/myapp/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. Executed `algokit bootstrap all` in {current_working_directory}/myapp 🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 diff --git a/tests/init/test_init.test_init_template_with_python_task_fails_on_missing_python.approved.txt b/tests/init/test_init.test_init_template_with_python_task_fails_on_missing_python.approved.txt index 78eeb38e..f3f672ca 100644 --- a/tests/init/test_init.test_init_template_with_python_task_fails_on_missing_python.approved.txt +++ b/tests/init/test_init.test_init_template_with_python_task_fails_on_missing_python.approved.txt @@ -3,6 +3,12 @@ DEBUG: No .algokit.toml file found in the project directory. WARNING: Community templates have not been reviewed, and can execute arbitrary code. Please inspect the template repository, and pay particular attention to the values of _tasks, _migrations and _jinja_extensions in copier.yml DEBUG: template source = {current_working_directory}/dummy_template@HEAD +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. diff --git a/tests/init/test_init.test_init_template_with_python_task_works.approved.txt b/tests/init/test_init.test_init_template_with_python_task_works.approved.txt index 7177cd1e..96edfe9f 100644 --- a/tests/init/test_init.test_init_template_with_python_task_works.approved.txt +++ b/tests/init/test_init.test_init_template_with_python_task_works.approved.txt @@ -3,13 +3,25 @@ DEBUG: No .algokit.toml file found in the project directory. WARNING: Community templates have not been reviewed, and can execute arbitrary code. Please inspect the template repository, and pay particular attention to the values of _tasks, _migrations and _jinja_extensions in copier.yml DEBUG: template source = {current_working_directory}/dummy_template@HEAD +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. Starting template copy and render at {current_working_directory}/myapp... DEBUG: final clone URL = {current_working_directory}/dummy_template Template render complete! -DEBUG: Attempting to load project config from {current_working_directory}/myapp/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. Executed `algokit bootstrap all` in {current_working_directory}/myapp 🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 diff --git a/tests/init/test_init.test_init_use_existing_folder.approved.txt b/tests/init/test_init.test_init_use_existing_folder.approved.txt index 9b164b6e..bcde29ed 100644 --- a/tests/init/test_init.test_init_use_existing_folder.approved.txt +++ b/tests/init/test_init.test_init_use_existing_folder.approved.txt @@ -5,6 +5,12 @@ Please inspect the template repository, and pay particular attention to the valu DEBUG: template source = {test_parent_directory}/copier-helloworld.bundle WARNING: Re-using existing directory, this is not recommended because if project generation fails, then we can't automatically cleanup. ? Continue anyway? (y/N) +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. @@ -12,7 +18,13 @@ Starting template copy and render at {current_working_directory}/myapp... No git tags found in template; using HEAD as ref DEBUG: final clone URL = {test_parent_directory}/copier-helloworld.bundle Template render complete! -DEBUG: Attempting to load project config from {current_working_directory}/myapp/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. Executed `algokit bootstrap all` in {current_working_directory}/myapp 🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 diff --git a/tests/init/test_init.test_init_with_any_template_url_get_community_warning.approved.txt b/tests/init/test_init.test_init_with_any_template_url_get_community_warning.approved.txt index a16a38fd..0a8503af 100644 --- a/tests/init/test_init.test_init_with_any_template_url_get_community_warning.approved.txt +++ b/tests/init/test_init.test_init_with_any_template_url_get_community_warning.approved.txt @@ -4,12 +4,23 @@ WARNING: Community templates have not been reviewed, and can execute arbitrary c Please inspect the template repository, and pay particular attention to the values of _tasks, _migrations and _jinja_extensions in copier.yml ? Continue anyway? (y/N) DEBUG: template source = gh:algorandfoundation/algokit-beaker-default-template +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. Starting template copy and render at {current_working_directory}/myapp... DEBUG: final clone URL = https://github.com/algorandfoundation/algokit-beaker-default-template.git Template render complete! +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. 🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 Your selected template comes from: ➡ī¸ https://github.com/algorandfoundation/algokit-beaker-default-template diff --git a/tests/init/test_init.test_init_with_any_template_url_get_community_warning_with_unsafe_tag.approved.txt b/tests/init/test_init.test_init_with_any_template_url_get_community_warning_with_unsafe_tag.approved.txt index a95a5151..9d1a1cf0 100644 --- a/tests/init/test_init.test_init_with_any_template_url_get_community_warning_with_unsafe_tag.approved.txt +++ b/tests/init/test_init.test_init_with_any_template_url_get_community_warning_with_unsafe_tag.approved.txt @@ -3,12 +3,23 @@ DEBUG: No .algokit.toml file found in the project directory. WARNING: Community templates have not been reviewed, and can execute arbitrary code. Please inspect the template repository, and pay particular attention to the values of _tasks, _migrations and _jinja_extensions in copier.yml DEBUG: template source = gh:algorandfoundation/algokit-beaker-default-template +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. Starting template copy and render at {current_working_directory}/myapp... DEBUG: final clone URL = https://github.com/algorandfoundation/algokit-beaker-default-template.git Template render complete! +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. 🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 Your selected template comes from: ➡ī¸ https://github.com/algorandfoundation/algokit-beaker-default-template diff --git a/tests/init/test_init.test_init_with_custom_env.approved.txt b/tests/init/test_init.test_init_with_custom_env.approved.txt index 8aa89f57..25c61b33 100644 --- a/tests/init/test_init.test_init_with_custom_env.approved.txt +++ b/tests/init/test_init.test_init_with_custom_env.approved.txt @@ -1,12 +1,23 @@ DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. DEBUG: template source = gh:algorandfoundation/algokit-beaker-default-template +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. Starting template copy and render at {current_working_directory}/myapp... DEBUG: final clone URL = https://github.com/algorandfoundation/algokit-beaker-default-template.git Template render complete! +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. 🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 Your selected template comes from: ➡ī¸ https://github.com/algorandfoundation/algokit-beaker-default-template diff --git a/tests/init/test_init.test_init_with_official_template_name.approved.txt b/tests/init/test_init.test_init_with_official_template_name.approved.txt index 8aa89f57..25c61b33 100644 --- a/tests/init/test_init.test_init_with_official_template_name.approved.txt +++ b/tests/init/test_init.test_init_with_official_template_name.approved.txt @@ -1,12 +1,23 @@ DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. DEBUG: template source = gh:algorandfoundation/algokit-beaker-default-template +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. Starting template copy and render at {current_working_directory}/myapp... DEBUG: final clone URL = https://github.com/algorandfoundation/algokit-beaker-default-template.git Template render complete! +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. 🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 Your selected template comes from: ➡ī¸ https://github.com/algorandfoundation/algokit-beaker-default-template diff --git a/tests/init/test_init.test_init_with_official_template_name_and_hash.approved.txt b/tests/init/test_init.test_init_with_official_template_name_and_hash.approved.txt index 61200080..bc32a9d1 100644 --- a/tests/init/test_init.test_init_with_official_template_name_and_hash.approved.txt +++ b/tests/init/test_init.test_init_with_official_template_name_and_hash.approved.txt @@ -1,13 +1,25 @@ DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. DEBUG: template source = gh:algorandfoundation/algokit-beaker-default-template@96fc7fd766fac607cdf5d69ee6e85ade04dddd47 +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. Starting template copy and render at {current_working_directory}/myapp... DEBUG: final clone URL = https://github.com/algorandfoundation/algokit-beaker-default-template.git Template render complete! -DEBUG: Attempting to load project config from {current_working_directory}/myapp/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. Executed `algokit bootstrap all` in {current_working_directory}/myapp 🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 diff --git a/tests/init/test_init.test_init_wizard_v2_append_to_vscode_workspace.approved.txt b/tests/init/test_init.test_init_wizard_v2_append_to_vscode_workspace.approved.txt new file mode 100644 index 00000000..b41660a1 --- /dev/null +++ b/tests/init/test_init.test_init_wizard_v2_append_to_vscode_workspace.approved.txt @@ -0,0 +1,23 @@ +DEBUG: template source = gh:algorandfoundation/algokit-beaker-default-template +DEBUG: Attempting to load project config from {current_working_directory}/myapp2/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: project path = {current_working_directory}/myapp2 +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: Workspace structure detected! Moving the project to be instantiated into {current_working_directory}/projects +Starting template copy and render at {current_working_directory}/projects/myapp2... +DEBUG: final clone URL = https://github.com/algorandfoundation/algokit-beaker-default-template.git +Template render complete! +DEBUG: Attempting to load project config from {current_working_directory}/projects/myapp2/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/projects/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/projects/myapp2/.algokit.toml +Executed `algokit bootstrap all` in {current_working_directory}/projects/myapp2 +🙌 Project initialized at `myapp2`! For template specific next steps, consult the documentation of your selected template 🧐 +Your selected template comes from: +➡ī¸ https://github.com/algorandfoundation/algokit-beaker-default-template +WARNING: Invalid JSON format in the workspace file {current_working_directory}/myapp.code-workspace. Expecting value: line 1 column 1 (char 0) +VSCode configuration detected in project directory, and 'code' command is available on path, attempting to launch VSCode +DEBUG: Running '/bin/code {current_working_directory}/myapp.code-workspace {current_working_directory}/projects/myapp2/README.md' in '{current_working_directory}' +DEBUG: /bin/code: Launch project diff --git a/tests/init/test_init.test_init_wizard_v2_flow.Custom Template.approved.txt b/tests/init/test_init.test_init_wizard_v2_flow.Custom Template.approved.txt index 978f9d5c..b9d5f4d7 100644 --- a/tests/init/test_init.test_init_wizard_v2_flow.Custom Template.approved.txt +++ b/tests/init/test_init.test_init_wizard_v2_flow.Custom Template.approved.txt @@ -22,18 +22,29 @@ Valid examples: - ~/path/to/git/repo - ~/path/to/git/repo.bundle -? Custom template URL: +? Custom template URL: DEBUG: template source = gh:robdmoore/copier-helloworld +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. -DEBUG: Attempting to load project config from {current_working_directory}/myapp/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: Workspace structure is ready! The project is to be placed under {current_working_directory}/myapp/projects/myapp Starting template copy and render at {current_working_directory}/myapp/projects/myapp... No git tags found in template; using HEAD as ref DEBUG: final clone URL = https://github.com/robdmoore/copier-helloworld.git Template render complete! -DEBUG: Attempting to load project config from {current_working_directory}/myapp/projects/myapp/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. Executed `algokit bootstrap all` in {current_working_directory}/myapp/projects/myapp 🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 diff --git a/tests/init/test_init.test_init_wizard_v2_flow.DApp Frontend.approved.txt b/tests/init/test_init.test_init_wizard_v2_flow.DApp Frontend.approved.txt index 2bd0a90b..bfccadba 100644 --- a/tests/init/test_init.test_init_wizard_v2_flow.DApp Frontend.approved.txt +++ b/tests/init/test_init.test_init_wizard_v2_flow.DApp Frontend.approved.txt @@ -7,16 +7,29 @@ Smart Contracts & DApp Frontend 🎛ī¸ Custom Template 🛠ī¸ DEBUG: selected project_type = DApp Frontend đŸ–Ĩī¸ DEBUG: template source = gh:robdmoore/copier-helloworld +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. -Starting template copy and render at {current_working_directory}/myapp... +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: Workspace structure is ready! The project is to be placed under {current_working_directory}/myapp/projects/myapp +Starting template copy and render at {current_working_directory}/myapp/projects/myapp... No git tags found in template; using HEAD as ref DEBUG: final clone URL = https://github.com/robdmoore/copier-helloworld.git Template render complete! -DEBUG: Attempting to load project config from {current_working_directory}/myapp/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. -Executed `algokit bootstrap all` in {current_working_directory}/myapp +Executed `algokit bootstrap all` in {current_working_directory}/myapp/projects/myapp 🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 Your selected template comes from: ➡ī¸ https://github.com/robdmoore/copier-helloworld diff --git a/tests/init/test_init.test_init_wizard_v2_flow.Full Stack.approved.txt b/tests/init/test_init.test_init_wizard_v2_flow.Full Stack.approved.txt index cc33f877..4a854df8 100644 --- a/tests/init/test_init.test_init_wizard_v2_flow.Full Stack.approved.txt +++ b/tests/init/test_init.test_init_wizard_v2_flow.Full Stack.approved.txt @@ -12,6 +12,12 @@ TypeScript 📘 PyTeal 🔧 DEBUG: selected language = ContractLanguage.PYTHON DEBUG: template source = gh:robdmoore/copier-helloworld +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. @@ -19,7 +25,13 @@ Starting template copy and render at {current_working_directory}/myapp... No git tags found in template; using HEAD as ref DEBUG: final clone URL = https://github.com/robdmoore/copier-helloworld.git Template render complete! -DEBUG: Attempting to load project config from {current_working_directory}/myapp/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. Executed `algokit bootstrap all` in {current_working_directory}/myapp 🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 diff --git a/tests/init/test_init.test_init_wizard_v2_flow.Smart Contract.approved.txt b/tests/init/test_init.test_init_wizard_v2_flow.Smart Contract.approved.txt index ef6a075c..bfca61a3 100644 --- a/tests/init/test_init.test_init_wizard_v2_flow.Smart Contract.approved.txt +++ b/tests/init/test_init.test_init_wizard_v2_flow.Smart Contract.approved.txt @@ -12,16 +12,29 @@ TypeScript 📘 PyTeal 🔧 DEBUG: selected language = ContractLanguage.PYTHON DEBUG: template source = gh:robdmoore/copier-helloworld +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. -Starting template copy and render at {current_working_directory}/myapp... +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: Workspace structure is ready! The project is to be placed under {current_working_directory}/myapp/projects/myapp +Starting template copy and render at {current_working_directory}/myapp/projects/myapp... No git tags found in template; using HEAD as ref DEBUG: final clone URL = https://github.com/robdmoore/copier-helloworld.git Template render complete! -DEBUG: Attempting to load project config from {current_working_directory}/myapp/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. -Executed `algokit bootstrap all` in {current_working_directory}/myapp +Executed `algokit bootstrap all` in {current_working_directory}/myapp/projects/myapp 🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 Your selected template comes from: ➡ī¸ https://github.com/robdmoore/copier-helloworld diff --git a/tests/bootstrap/test_bootstrap_env.test_bootstrap_network_prefixed_env_dotenv_exists.approved.txt b/tests/init/test_init.test_init_wizard_v2_github_folder_no_workspace.approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_env.test_bootstrap_network_prefixed_env_dotenv_exists.approved.txt rename to tests/init/test_init.test_init_wizard_v2_github_folder_no_workspace.approved.txt diff --git a/tests/deploy/__init__.py b/tests/init/test_init.test_init_wizard_v2_github_folder_with_workspace.approved.txt similarity index 100% rename from tests/deploy/__init__.py rename to tests/init/test_init.test_init_wizard_v2_github_folder_with_workspace.approved.txt diff --git a/tests/init/test_init_with_bootstrap.py b/tests/init/test_init_with_bootstrap.py index c1fb089b..28fe37bc 100644 --- a/tests/init/test_init_with_bootstrap.py +++ b/tests/init/test_init_with_bootstrap.py @@ -15,6 +15,17 @@ GIT_BUNDLE_PATH = PARENT_DIRECTORY / "copier-helloworld.bundle" +def _remove_project_paths(output: str) -> str: + lines = [ + "DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml" + if "DEBUG: Attempting to load project config from " in line + else line + for line in output.splitlines() + ] + + return "\n".join(lines) + + def make_output_scrubber(*extra_scrubbers: Callable[[str], str], **extra_tokens: str) -> Scrubber: default_tokens = {"test_parent_directory": str(PARENT_DIRECTORY)} tokens = default_tokens | extra_tokens @@ -24,6 +35,7 @@ def make_output_scrubber(*extra_scrubbers: Callable[[str], str], **extra_tokens: TokenScrubber(tokens=tokens), TokenScrubber(tokens={"test_parent_directory": str(PARENT_DIRECTORY).replace("\\", "/")}), lambda t: t.replace("{test_parent_directory}\\", "{test_parent_directory}/"), + _remove_project_paths, ) diff --git a/tests/init/test_init_with_bootstrap.test_init_bootstrap_broken_poetry.approved.txt b/tests/init/test_init_with_bootstrap.test_init_bootstrap_broken_poetry.approved.txt index 49d09a3c..f3bd5307 100644 --- a/tests/init/test_init_with_bootstrap.test_init_bootstrap_broken_poetry.approved.txt +++ b/tests/init/test_init_with_bootstrap.test_init_bootstrap_broken_poetry.approved.txt @@ -5,6 +5,12 @@ Please inspect the template repository, and pay particular attention to the valu DEBUG: template source = {test_parent_directory}/copier-helloworld.bundle WARNING: Re-using existing directory, this is not recommended because if project generation fails, then we can't automatically cleanup. ? Continue anyway? (y/N) +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. @@ -12,7 +18,15 @@ Starting template copy and render at {current_working_directory}/myapp... DEBUG: final clone URL = {test_parent_directory}/copier-helloworld.bundle No git tags found in template; using HEAD as ref Template render complete! -DEBUG: Attempting to load project config from {current_working_directory}/myapp/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. DEBUG: Checking {current_working_directory}/myapp for bootstrapping needs DEBUG: Running `algokit bootstrap poetry` diff --git a/tests/init/test_init_with_bootstrap.test_init_bootstrap_version_fail.approved.txt b/tests/init/test_init_with_bootstrap.test_init_bootstrap_version_fail.approved.txt index 711278ec..a8318987 100644 --- a/tests/init/test_init_with_bootstrap.test_init_bootstrap_version_fail.approved.txt +++ b/tests/init/test_init_with_bootstrap.test_init_bootstrap_version_fail.approved.txt @@ -5,6 +5,11 @@ Please inspect the template repository, and pay particular attention to the valu DEBUG: template source = {test_parent_directory}/copier-helloworld.bundle WARNING: Re-using existing directory, this is not recommended because if project generation fails, then we can't automatically cleanup. ? Continue anyway? (y/N) +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: project path = {current_working_directory}/myapp DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. @@ -12,7 +17,12 @@ Starting template copy and render at {current_working_directory}/myapp... DEBUG: final clone URL = {test_parent_directory}/copier-helloworld.bundle No git tags found in template; using HEAD as ref Template render complete! -DEBUG: Attempting to load project config from {current_working_directory}/myapp/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml ERROR: Received an error while attempting bootstrap: This template requires AlgoKit version 999.99.99 or higher, but you have AlgoKit version {current_version}. Please update AlgoKit. ERROR: Bootstrap failed. Once any errors above are resolved, you can run `algokit bootstrap` in {current_working_directory}/myapp 🙌 Project initialized at `myapp`! For template specific next steps, consult the documentation of your selected template 🧐 diff --git a/tests/project/__init__.py b/tests/project/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/project/bootstrap/__init__.py b/tests/project/bootstrap/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/bootstrap/test_bootstrap.py b/tests/project/bootstrap/test_bootstrap.py similarity index 80% rename from tests/bootstrap/test_bootstrap.py rename to tests/project/bootstrap/test_bootstrap.py index ae1fdc9e..5c332d96 100644 --- a/tests/bootstrap/test_bootstrap.py +++ b/tests/project/bootstrap/test_bootstrap.py @@ -3,7 +3,7 @@ def test_bootstrap_help() -> None: - result = invoke("bootstrap -h") + result = invoke("project bootstrap -h") assert result.exit_code == 0 verify(result.output) diff --git a/tests/bootstrap/test_bootstrap.test_bootstrap_help.approved.txt b/tests/project/bootstrap/test_bootstrap.test_bootstrap_help.approved.txt similarity index 92% rename from tests/bootstrap/test_bootstrap.test_bootstrap_help.approved.txt rename to tests/project/bootstrap/test_bootstrap.test_bootstrap_help.approved.txt index b4a9b95e..84a39800 100644 --- a/tests/bootstrap/test_bootstrap.test_bootstrap_help.approved.txt +++ b/tests/project/bootstrap/test_bootstrap.test_bootstrap_help.approved.txt @@ -1,4 +1,4 @@ -Usage: algokit bootstrap [OPTIONS] COMMAND [ARGS]... +Usage: algokit project bootstrap [OPTIONS] COMMAND [ARGS]... Expedited initial setup for any developer by installing and configuring dependencies and other key development environment setup activities. diff --git a/tests/bootstrap/test_bootstrap_all.py b/tests/project/bootstrap/test_bootstrap_all.py similarity index 53% rename from tests/bootstrap/test_bootstrap_all.py rename to tests/project/bootstrap/test_bootstrap_all.py index b8606733..3f123f7c 100644 --- a/tests/bootstrap/test_bootstrap_all.py +++ b/tests/project/bootstrap/test_bootstrap_all.py @@ -1,3 +1,5 @@ +from pathlib import Path + import pytest from _pytest.tmpdir import TempPathFactory from algokit.core.conf import ALGOKIT_CONFIG, get_current_package_version @@ -7,11 +9,46 @@ from tests.utils.click_invoker import invoke +def _setup_workspace(cwd: Path) -> None: + """ + Sets up the workspace configuration. + """ + algokit_config_path = cwd / ".algokit.toml" + algokit_config_path.write_text( + """ + [project] + type = "workspace" + projects_root_dir = 'artifacts' + """ + ) + + +def _setup_standalone_project(cwd: Path, project_name: str, project_type: str) -> None: + """ + Sets up a standalone project of a specified type within the workspace. + """ + project_dir = cwd / "artifacts" / project_name + project_dir.mkdir(parents=True) + project_config_path = project_dir / ".algokit.toml" + project_config_path.write_text( + f""" + [project] + type = "{project_type}" + name = "{project_name}" + """ + ) + (project_dir / ".env.template").touch() + if project_type == "contract": + (project_dir / "poetry.toml").touch() + elif project_type == "frontend": + (project_dir / "package.json").touch() + + def test_bootstrap_all_empty(tmp_path_factory: TempPathFactory) -> None: cwd = tmp_path_factory.mktemp("cwd") result = invoke( - "bootstrap all", + "project bootstrap all", cwd=cwd, ) @@ -24,7 +61,7 @@ def test_bootstrap_all_algokit_min_version(tmp_path_factory: TempPathFactory) -> current_version = get_current_package_version() (cwd / ALGOKIT_CONFIG).write_text('[algokit]\nmin_version = "999.99.99"\n') result = invoke( - "bootstrap all", + "project bootstrap all", cwd=cwd, ) @@ -37,7 +74,7 @@ def test_bootstrap_all_algokit_min_version_ignore_error(tmp_path_factory: TempPa current_version = get_current_package_version() (cwd / ALGOKIT_CONFIG).write_text('[algokit]\nmin_version = "999.99.99"\n') result = invoke( - "bootstrap --force all", + "project bootstrap --force all", cwd=cwd, ) @@ -50,7 +87,7 @@ def test_bootstrap_all_env(tmp_path_factory: TempPathFactory) -> None: (cwd / ".env.template").touch() result = invoke( - "bootstrap all", + "project bootstrap all", cwd=cwd, ) @@ -64,7 +101,7 @@ def test_bootstrap_all_poetry(tmp_path_factory: TempPathFactory) -> None: (cwd / "poetry.toml").touch() result = invoke( - "bootstrap all", + "project bootstrap all", cwd=cwd, ) @@ -78,7 +115,7 @@ def test_bootstrap_all_npm(tmp_path_factory: TempPathFactory, request: pytest.Fi (cwd / "package.json").touch() result = invoke( - "bootstrap all", + "project bootstrap all", cwd=cwd, ) @@ -92,7 +129,7 @@ def test_bootstrap_all_poetry_via_pyproject(tmp_path_factory: TempPathFactory) - (cwd / "pyproject.toml").write_text("[tool.poetry]", encoding="utf-8") result = invoke( - "bootstrap all", + "project bootstrap all", cwd=cwd, ) @@ -115,7 +152,7 @@ def test_bootstrap_all_skip_dirs(tmp_path_factory: TempPathFactory) -> None: (cwd / "double_nested_dir" / "nest2" / "file.txt").touch() result = invoke( - "bootstrap all", + "project bootstrap all", cwd=cwd, ) @@ -132,9 +169,58 @@ def test_bootstrap_all_sub_dir(tmp_path_factory: TempPathFactory) -> None: (cwd / "live_dir" / "poetry.toml").touch() result = invoke( - "bootstrap all", + "project bootstrap all", cwd=cwd, ) assert result.exit_code == 0 verify(result.output) + + +@pytest.mark.usefixtures("proc_mock") +def test_bootstrap_all_projects_name_filter(tmp_path_factory: TempPathFactory) -> None: + cwd = tmp_path_factory.mktemp("cwd") + _setup_workspace(cwd) + _setup_standalone_project(cwd, "project_1", "contract") + result = invoke("project bootstrap all --project-name project_1", cwd=cwd) + assert result.exit_code == 0 + verify(result.output) + + +@pytest.mark.usefixtures("proc_mock") +def test_bootstrap_all_projects_name_filter_not_found(tmp_path_factory: TempPathFactory) -> None: + cwd = tmp_path_factory.mktemp("cwd") + _setup_workspace(cwd) + _setup_standalone_project(cwd, "project_1", "contract") + result = invoke("project bootstrap all --project-name project_2", cwd=cwd) + assert result.exit_code == 0 + verify(result.output) + + +@pytest.mark.usefixtures("proc_mock") +def test_bootstrap_all_projects_type_filter(tmp_path_factory: TempPathFactory) -> None: + cwd = tmp_path_factory.mktemp("cwd") + _setup_workspace(cwd) + _setup_standalone_project(cwd, "project_1", "contract") + _setup_standalone_project(cwd, "project_2", "contract") + _setup_standalone_project(cwd, "project_3", "contract") + _setup_standalone_project(cwd, "project_4", "frontend") + + result = invoke("project bootstrap all --type frontend", cwd=cwd) + + assert result.exit_code == 0 + verify(result.output.replace(".cmd", "")) + + +@pytest.mark.usefixtures("proc_mock") +def test_bootstrap_all_projects_type_filter_not_found(tmp_path_factory: TempPathFactory) -> None: + cwd = tmp_path_factory.mktemp("cwd") + _setup_workspace(cwd) + _setup_standalone_project(cwd, "project_1", "contract") + _setup_standalone_project(cwd, "project_2", "contract") + _setup_standalone_project(cwd, "project_3", "contract") + + result = invoke("project bootstrap all --type frontend", cwd=cwd) + + assert result.exit_code == 0 + verify(result.output) diff --git a/tests/bootstrap/test_bootstrap_all.test_bootstrap_all_algokit_min_version.approved.txt b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_algokit_min_version.approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_all.test_bootstrap_all_algokit_min_version.approved.txt rename to tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_algokit_min_version.approved.txt diff --git a/tests/bootstrap/test_bootstrap_all.test_bootstrap_all_algokit_min_version_ignore_error.approved.txt b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_algokit_min_version_ignore_error.approved.txt similarity index 82% rename from tests/bootstrap/test_bootstrap_all.test_bootstrap_all_algokit_min_version_ignore_error.approved.txt rename to tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_algokit_min_version_ignore_error.approved.txt index 9c466174..3c45df04 100644 --- a/tests/bootstrap/test_bootstrap_all.test_bootstrap_all_algokit_min_version_ignore_error.approved.txt +++ b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_algokit_min_version_ignore_error.approved.txt @@ -1,5 +1,6 @@ DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml WARNING: This template requires AlgoKit version 999.99.99 or higher, but you have AlgoKit version {current_version}. Please update AlgoKit. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: Checking {current_working_directory} for bootstrapping needs DEBUG: Skipping {current_working_directory}/.algokit.toml Finished bootstrapping {current_working_directory} diff --git a/tests/bootstrap/test_bootstrap_all.test_bootstrap_all_empty.approved.txt b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_empty.approved.txt similarity index 64% rename from tests/bootstrap/test_bootstrap_all.test_bootstrap_all_empty.approved.txt rename to tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_empty.approved.txt index 63a5f23a..9291edad 100644 --- a/tests/bootstrap/test_bootstrap_all.test_bootstrap_all_empty.approved.txt +++ b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_empty.approved.txt @@ -1,4 +1,6 @@ DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: Checking {current_working_directory} for bootstrapping needs Finished bootstrapping {current_working_directory} diff --git a/tests/bootstrap/test_bootstrap_all.test_bootstrap_all_env.approved.txt b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_env.approved.txt similarity index 81% rename from tests/bootstrap/test_bootstrap_all.test_bootstrap_all_env.approved.txt rename to tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_env.approved.txt index ac2c6eae..e67baaa2 100644 --- a/tests/bootstrap/test_bootstrap_all.test_bootstrap_all_env.approved.txt +++ b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_env.approved.txt @@ -1,5 +1,7 @@ DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: Checking {current_working_directory} for bootstrapping needs DEBUG: Running `algokit bootstrap env` DEBUG: {current_working_directory}/.env doesn't exist yet diff --git a/tests/bootstrap/test_bootstrap_all.test_bootstrap_all_npm[linux].approved.txt b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_npm[linux].approved.txt similarity index 76% rename from tests/bootstrap/test_bootstrap_all.test_bootstrap_all_npm[linux].approved.txt rename to tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_npm[linux].approved.txt index 83fb9ecc..cb3b02f6 100644 --- a/tests/bootstrap/test_bootstrap_all.test_bootstrap_all_npm[linux].approved.txt +++ b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_npm[linux].approved.txt @@ -1,5 +1,7 @@ DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: Checking {current_working_directory} for bootstrapping needs DEBUG: Running `algokit bootstrap npm` Installing npm dependencies diff --git a/tests/bootstrap/test_bootstrap_all.test_bootstrap_all_npm[macOS].approved.txt b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_npm[macOS].approved.txt similarity index 76% rename from tests/bootstrap/test_bootstrap_all.test_bootstrap_all_npm[macOS].approved.txt rename to tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_npm[macOS].approved.txt index 83fb9ecc..cb3b02f6 100644 --- a/tests/bootstrap/test_bootstrap_all.test_bootstrap_all_npm[macOS].approved.txt +++ b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_npm[macOS].approved.txt @@ -1,5 +1,7 @@ DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: Checking {current_working_directory} for bootstrapping needs DEBUG: Running `algokit bootstrap npm` Installing npm dependencies diff --git a/tests/bootstrap/test_bootstrap_all.test_bootstrap_all_npm[windows].approved.txt b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_npm[windows].approved.txt similarity index 76% rename from tests/bootstrap/test_bootstrap_all.test_bootstrap_all_npm[windows].approved.txt rename to tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_npm[windows].approved.txt index d0f15727..3d7c50d6 100644 --- a/tests/bootstrap/test_bootstrap_all.test_bootstrap_all_npm[windows].approved.txt +++ b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_npm[windows].approved.txt @@ -1,5 +1,7 @@ DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: Checking {current_working_directory} for bootstrapping needs DEBUG: Running `algokit bootstrap npm` Installing npm dependencies diff --git a/tests/bootstrap/test_bootstrap_all.test_bootstrap_all_poetry.approved.txt b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_poetry.approved.txt similarity index 81% rename from tests/bootstrap/test_bootstrap_all.test_bootstrap_all_poetry.approved.txt rename to tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_poetry.approved.txt index 5f2c73f3..74312a06 100644 --- a/tests/bootstrap/test_bootstrap_all.test_bootstrap_all_poetry.approved.txt +++ b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_poetry.approved.txt @@ -1,5 +1,7 @@ DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: Checking {current_working_directory} for bootstrapping needs DEBUG: Running `algokit bootstrap poetry` DEBUG: Running 'poetry --version' in '{current_working_directory}' diff --git a/tests/bootstrap/test_bootstrap_all.test_bootstrap_all_poetry_via_pyproject.approved.txt b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_poetry_via_pyproject.approved.txt similarity index 81% rename from tests/bootstrap/test_bootstrap_all.test_bootstrap_all_poetry_via_pyproject.approved.txt rename to tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_poetry_via_pyproject.approved.txt index ccd2bddc..5ae3d913 100644 --- a/tests/bootstrap/test_bootstrap_all.test_bootstrap_all_poetry_via_pyproject.approved.txt +++ b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_poetry_via_pyproject.approved.txt @@ -1,5 +1,7 @@ DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: Checking {current_working_directory} for bootstrapping needs DEBUG: Running `algokit bootstrap poetry` DEBUG: Running 'poetry --version' in '{current_working_directory}' diff --git a/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_projects_filter.approved.txt b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_projects_filter.approved.txt new file mode 100644 index 00000000..cabcc748 --- /dev/null +++ b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_projects_filter.approved.txt @@ -0,0 +1,26 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No 'min_version' specified in .algokit.toml file. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: Skipping {current_working_directory}/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/artifacts/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Checking {current_working_directory}/artifacts for bootstrapping needs +DEBUG: Attempting to load project config from {current_working_directory}/artifacts/project1/.algokit.toml +DEBUG: Checking {current_working_directory}/artifacts/project1 for bootstrapping needs +DEBUG: Running `algokit bootstrap env` +DEBUG: {current_working_directory}/artifacts/project1/.env doesn't exist yet +DEBUG: {current_working_directory}/artifacts/project1/.env.template exists +Copying {current_working_directory}/artifacts/project1/.env.template to {current_working_directory}/artifacts/project1/.env and prompting for empty values +DEBUG: Running `algokit bootstrap poetry` +DEBUG: Running 'poetry --version' in '{current_working_directory}' +DEBUG: poetry: STDOUT +DEBUG: poetry: STDERR +Installing Python dependencies and setting up Python virtual environment via Poetry +DEBUG: Running 'poetry install' in '{current_working_directory}/artifacts/project1' +poetry: STDOUT +poetry: STDERR +DEBUG: Skipping {current_working_directory}/artifacts/project1/.algokit.toml +DEBUG: Skipping {current_working_directory}/artifacts/project1/.env +DEBUG: Skipping {current_working_directory}/artifacts/project1/.env.template +DEBUG: Skipping {current_working_directory}/artifacts/project1/poetry.toml +Finished bootstrapping {current_working_directory} diff --git a/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_projects_filter_not_found.approved.txt b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_projects_filter_not_found.approved.txt new file mode 100644 index 00000000..4ae7c668 --- /dev/null +++ b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_projects_filter_not_found.approved.txt @@ -0,0 +1,12 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No 'min_version' specified in .algokit.toml file. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: Skipping {current_working_directory}/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/artifacts/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Checking {current_working_directory}/artifacts for bootstrapping needs +DEBUG: Attempting to load project config from {current_working_directory}/artifacts/project1/.algokit.toml +DEBUG: Skipping {current_working_directory}/artifacts/project1/.algokit.toml +DEBUG: Skipping {current_working_directory}/artifacts/project1/.env.template +DEBUG: Skipping {current_working_directory}/artifacts/project1/poetry.toml +Finished bootstrapping {current_working_directory} diff --git a/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_projects_name_filter.approved.txt b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_projects_name_filter.approved.txt new file mode 100644 index 00000000..79497e84 --- /dev/null +++ b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_projects_name_filter.approved.txt @@ -0,0 +1,26 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No 'min_version' specified in .algokit.toml file. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: Skipping {current_working_directory}/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/artifacts/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Checking {current_working_directory}/artifacts for bootstrapping needs +DEBUG: Attempting to load project config from {current_working_directory}/artifacts/project_1/.algokit.toml +DEBUG: Checking {current_working_directory}/artifacts/project_1 for bootstrapping needs +DEBUG: Running `algokit bootstrap env` +DEBUG: {current_working_directory}/artifacts/project_1/.env doesn't exist yet +DEBUG: {current_working_directory}/artifacts/project_1/.env.template exists +Copying {current_working_directory}/artifacts/project_1/.env.template to {current_working_directory}/artifacts/project_1/.env and prompting for empty values +DEBUG: Running `algokit bootstrap poetry` +DEBUG: Running 'poetry --version' in '{current_working_directory}' +DEBUG: poetry: STDOUT +DEBUG: poetry: STDERR +Installing Python dependencies and setting up Python virtual environment via Poetry +DEBUG: Running 'poetry install' in '{current_working_directory}/artifacts/project_1' +poetry: STDOUT +poetry: STDERR +DEBUG: Skipping {current_working_directory}/artifacts/project_1/.algokit.toml +DEBUG: Skipping {current_working_directory}/artifacts/project_1/.env +DEBUG: Skipping {current_working_directory}/artifacts/project_1/.env.template +DEBUG: Skipping {current_working_directory}/artifacts/project_1/poetry.toml +Finished bootstrapping {current_working_directory} diff --git a/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_projects_name_filter_not_found.approved.txt b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_projects_name_filter_not_found.approved.txt new file mode 100644 index 00000000..45d7103a --- /dev/null +++ b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_projects_name_filter_not_found.approved.txt @@ -0,0 +1,12 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No 'min_version' specified in .algokit.toml file. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: Skipping {current_working_directory}/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/artifacts/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Checking {current_working_directory}/artifacts for bootstrapping needs +DEBUG: Attempting to load project config from {current_working_directory}/artifacts/project_1/.algokit.toml +DEBUG: Skipping {current_working_directory}/artifacts/project_1/.algokit.toml +DEBUG: Skipping {current_working_directory}/artifacts/project_1/.env.template +DEBUG: Skipping {current_working_directory}/artifacts/project_1/poetry.toml +Finished bootstrapping {current_working_directory} diff --git a/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_projects_type_filter.approved.txt b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_projects_type_filter.approved.txt new file mode 100644 index 00000000..21548042 --- /dev/null +++ b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_projects_type_filter.approved.txt @@ -0,0 +1,35 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No 'min_version' specified in .algokit.toml file. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: Skipping {current_working_directory}/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/artifacts/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Checking {current_working_directory}/artifacts for bootstrapping needs +DEBUG: Attempting to load project config from {current_working_directory}/artifacts/project_1/.algokit.toml +DEBUG: Skipping {current_working_directory}/artifacts/project_1/.algokit.toml +DEBUG: Skipping {current_working_directory}/artifacts/project_1/.env.template +DEBUG: Skipping {current_working_directory}/artifacts/project_1/poetry.toml +DEBUG: Attempting to load project config from {current_working_directory}/artifacts/project_2/.algokit.toml +DEBUG: Skipping {current_working_directory}/artifacts/project_2/.algokit.toml +DEBUG: Skipping {current_working_directory}/artifacts/project_2/.env.template +DEBUG: Skipping {current_working_directory}/artifacts/project_2/poetry.toml +DEBUG: Attempting to load project config from {current_working_directory}/artifacts/project_3/.algokit.toml +DEBUG: Skipping {current_working_directory}/artifacts/project_3/.algokit.toml +DEBUG: Skipping {current_working_directory}/artifacts/project_3/.env.template +DEBUG: Skipping {current_working_directory}/artifacts/project_3/poetry.toml +DEBUG: Attempting to load project config from {current_working_directory}/artifacts/project_4/.algokit.toml +DEBUG: Checking {current_working_directory}/artifacts/project_4 for bootstrapping needs +DEBUG: Running `algokit bootstrap env` +DEBUG: {current_working_directory}/artifacts/project_4/.env doesn't exist yet +DEBUG: {current_working_directory}/artifacts/project_4/.env.template exists +Copying {current_working_directory}/artifacts/project_4/.env.template to {current_working_directory}/artifacts/project_4/.env and prompting for empty values +DEBUG: Running `algokit bootstrap npm` +Installing npm dependencies +DEBUG: Running 'npm install' in '{current_working_directory}/artifacts/project_4' +npm: STDOUT +npm: STDERR +DEBUG: Skipping {current_working_directory}/artifacts/project_4/.algokit.toml +DEBUG: Skipping {current_working_directory}/artifacts/project_4/.env +DEBUG: Skipping {current_working_directory}/artifacts/project_4/.env.template +DEBUG: Skipping {current_working_directory}/artifacts/project_4/package.json +Finished bootstrapping {current_working_directory} diff --git a/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_projects_type_filter_not_found.approved.txt b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_projects_type_filter_not_found.approved.txt new file mode 100644 index 00000000..2cf95a3d --- /dev/null +++ b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_projects_type_filter_not_found.approved.txt @@ -0,0 +1,20 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No 'min_version' specified in .algokit.toml file. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: Skipping {current_working_directory}/.algokit.toml +DEBUG: Attempting to load project config from {current_working_directory}/artifacts/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Checking {current_working_directory}/artifacts for bootstrapping needs +DEBUG: Attempting to load project config from {current_working_directory}/artifacts/project_1/.algokit.toml +DEBUG: Skipping {current_working_directory}/artifacts/project_1/.algokit.toml +DEBUG: Skipping {current_working_directory}/artifacts/project_1/.env.template +DEBUG: Skipping {current_working_directory}/artifacts/project_1/poetry.toml +DEBUG: Attempting to load project config from {current_working_directory}/artifacts/project_2/.algokit.toml +DEBUG: Skipping {current_working_directory}/artifacts/project_2/.algokit.toml +DEBUG: Skipping {current_working_directory}/artifacts/project_2/.env.template +DEBUG: Skipping {current_working_directory}/artifacts/project_2/poetry.toml +DEBUG: Attempting to load project config from {current_working_directory}/artifacts/project_3/.algokit.toml +DEBUG: Skipping {current_working_directory}/artifacts/project_3/.algokit.toml +DEBUG: Skipping {current_working_directory}/artifacts/project_3/.env.template +DEBUG: Skipping {current_working_directory}/artifacts/project_3/poetry.toml +Finished bootstrapping {current_working_directory} diff --git a/tests/bootstrap/test_bootstrap_all.test_bootstrap_all_skip_dirs.approved.txt b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_skip_dirs.approved.txt similarity index 51% rename from tests/bootstrap/test_bootstrap_all.test_bootstrap_all_skip_dirs.approved.txt rename to tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_skip_dirs.approved.txt index 85001956..ef2d1ae3 100644 --- a/tests/bootstrap/test_bootstrap_all.test_bootstrap_all_skip_dirs.approved.txt +++ b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_skip_dirs.approved.txt @@ -1,14 +1,26 @@ DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: Checking {current_working_directory} for bootstrapping needs DEBUG: Skipping {current_working_directory}/.venv DEBUG: Skipping {current_working_directory}/__pycache__ +DEBUG: Attempting to load project config from {current_working_directory}/boring_dir/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: Checking {current_working_directory}/boring_dir for bootstrapping needs DEBUG: Skipping {current_working_directory}/boring_dir/file.txt +DEBUG: Attempting to load project config from {current_working_directory}/double_nested_dir/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: Checking {current_working_directory}/double_nested_dir for bootstrapping needs +DEBUG: Attempting to load project config from {current_working_directory}/double_nested_dir/nest1/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: Checking {current_working_directory}/double_nested_dir/nest1 for bootstrapping needs +DEBUG: Attempting to load project config from {current_working_directory}/double_nested_dir/nest2/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: Checking {current_working_directory}/double_nested_dir/nest2 for bootstrapping needs DEBUG: Skipping {current_working_directory}/double_nested_dir/nest2/file.txt +DEBUG: Attempting to load project config from {current_working_directory}/empty_dir/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: Checking {current_working_directory}/empty_dir for bootstrapping needs DEBUG: Skipping {current_working_directory}/file.txt DEBUG: Skipping {current_working_directory}/node_modules diff --git a/tests/bootstrap/test_bootstrap_all.test_bootstrap_all_sub_dir.approved.txt b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_sub_dir.approved.txt similarity index 73% rename from tests/bootstrap/test_bootstrap_all.test_bootstrap_all_sub_dir.approved.txt rename to tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_sub_dir.approved.txt index 5b277e92..ec968e92 100644 --- a/tests/bootstrap/test_bootstrap_all.test_bootstrap_all_sub_dir.approved.txt +++ b/tests/project/bootstrap/test_bootstrap_all.test_bootstrap_all_sub_dir.approved.txt @@ -1,7 +1,13 @@ DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: Checking {current_working_directory} for bootstrapping needs +DEBUG: Attempting to load project config from {current_working_directory}/empty_dir/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: Checking {current_working_directory}/empty_dir for bootstrapping needs +DEBUG: Attempting to load project config from {current_working_directory}/live_dir/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: Checking {current_working_directory}/live_dir for bootstrapping needs DEBUG: Running `algokit bootstrap env` DEBUG: {current_working_directory}/live_dir/.env doesn't exist yet diff --git a/tests/bootstrap/test_bootstrap_env.py b/tests/project/bootstrap/test_bootstrap_env.py similarity index 95% rename from tests/bootstrap/test_bootstrap_env.py rename to tests/project/bootstrap/test_bootstrap_env.py index 7636f031..269128da 100644 --- a/tests/bootstrap/test_bootstrap_env.py +++ b/tests/project/bootstrap/test_bootstrap_env.py @@ -18,7 +18,7 @@ def test_bootstrap_env_no_files(tmp_path_factory: TempPathFactory) -> None: cwd = tmp_path_factory.mktemp("cwd") result = invoke( - "bootstrap env", + "project bootstrap env", cwd=cwd, ) @@ -32,7 +32,7 @@ def test_bootstrap_env_dotenv_exists(tmp_path_factory: TempPathFactory) -> None: (cwd / ".env.template").touch() result = invoke( - "bootstrap env", + "project bootstrap env", cwd=cwd, ) @@ -56,7 +56,7 @@ def test_bootstrap_network_prefixed_envs(env_file_name: str, tmp_path_factory: T (cwd / f"{env_file_name}.template").touch() result = invoke( - "bootstrap env", + "project bootstrap env", cwd=cwd, ) @@ -71,7 +71,7 @@ def test_bootstrap_env_multiple_templates(tmp_path_factory: TempPathFactory) -> (cwd / ".env.testnet.template").touch() result = invoke( - "bootstrap env", + "project bootstrap env", cwd=cwd, ) @@ -84,7 +84,7 @@ def test_bootstrap_env_dotenv_missing_template_exists(tmp_path_factory: TempPath (cwd / ".env.template").write_text("env_template_contents") result = invoke( - "bootstrap env", + "project bootstrap env", cwd=cwd, ) @@ -110,7 +110,7 @@ def test_bootstrap_env_dotenv_with_values(tmp_path_factory: TempPathFactory) -> ) result = invoke( - "bootstrap env", + "project bootstrap env", cwd=cwd, ) @@ -158,7 +158,7 @@ def test_bootstrap_env_dotenv_different_prompt_scenarios( mock_questionary_input.send_text("\n") # enter result = invoke( - "bootstrap env", + "project bootstrap env", cwd=cwd, ) diff --git a/tests/bootstrap/test_bootstrap_env.test_bootstrap_env_dotenv_different_prompt_scenarios.approved.txt b/tests/project/bootstrap/test_bootstrap_env.test_bootstrap_env_dotenv_different_prompt_scenarios.approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_env.test_bootstrap_env_dotenv_different_prompt_scenarios.approved.txt rename to tests/project/bootstrap/test_bootstrap_env.test_bootstrap_env_dotenv_different_prompt_scenarios.approved.txt diff --git a/tests/bootstrap/test_bootstrap_env.test_bootstrap_env_dotenv_exists.approved.txt b/tests/project/bootstrap/test_bootstrap_env.test_bootstrap_env_dotenv_exists.approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_env.test_bootstrap_env_dotenv_exists.approved.txt rename to tests/project/bootstrap/test_bootstrap_env.test_bootstrap_env_dotenv_exists.approved.txt diff --git a/tests/bootstrap/test_bootstrap_env.test_bootstrap_env_dotenv_missing_template_exists.approved.txt b/tests/project/bootstrap/test_bootstrap_env.test_bootstrap_env_dotenv_missing_template_exists.approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_env.test_bootstrap_env_dotenv_missing_template_exists.approved.txt rename to tests/project/bootstrap/test_bootstrap_env.test_bootstrap_env_dotenv_missing_template_exists.approved.txt diff --git a/tests/bootstrap/test_bootstrap_env.test_bootstrap_env_dotenv_with_values.approved.txt b/tests/project/bootstrap/test_bootstrap_env.test_bootstrap_env_dotenv_with_values.approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_env.test_bootstrap_env_dotenv_with_values.approved.txt rename to tests/project/bootstrap/test_bootstrap_env.test_bootstrap_env_dotenv_with_values.approved.txt diff --git a/tests/bootstrap/test_bootstrap_env.test_bootstrap_env_multiple_templates.approved.txt b/tests/project/bootstrap/test_bootstrap_env.test_bootstrap_env_multiple_templates.approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_env.test_bootstrap_env_multiple_templates.approved.txt rename to tests/project/bootstrap/test_bootstrap_env.test_bootstrap_env_multiple_templates.approved.txt diff --git a/tests/bootstrap/test_bootstrap_env.test_bootstrap_env_no_files.approved.txt b/tests/project/bootstrap/test_bootstrap_env.test_bootstrap_env_no_files.approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_env.test_bootstrap_env_no_files.approved.txt rename to tests/project/bootstrap/test_bootstrap_env.test_bootstrap_env_no_files.approved.txt diff --git a/tests/project/bootstrap/test_bootstrap_env.test_bootstrap_network_prefixed_env_dotenv_exists.approved.txt b/tests/project/bootstrap/test_bootstrap_env.test_bootstrap_network_prefixed_env_dotenv_exists.approved.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/bootstrap/test_bootstrap_env.test_bootstrap_network_prefixed_envs..env.approved.txt b/tests/project/bootstrap/test_bootstrap_env.test_bootstrap_network_prefixed_envs..env.approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_env.test_bootstrap_network_prefixed_envs..env.approved.txt rename to tests/project/bootstrap/test_bootstrap_env.test_bootstrap_network_prefixed_envs..env.approved.txt diff --git a/tests/bootstrap/test_bootstrap_env.test_bootstrap_network_prefixed_envs..env.localnet.approved.txt b/tests/project/bootstrap/test_bootstrap_env.test_bootstrap_network_prefixed_envs..env.localnet.approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_env.test_bootstrap_network_prefixed_envs..env.localnet.approved.txt rename to tests/project/bootstrap/test_bootstrap_env.test_bootstrap_network_prefixed_envs..env.localnet.approved.txt diff --git a/tests/bootstrap/test_bootstrap_env.test_bootstrap_network_prefixed_envs..env.localnet.template.approved.txt b/tests/project/bootstrap/test_bootstrap_env.test_bootstrap_network_prefixed_envs..env.localnet.template.approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_env.test_bootstrap_network_prefixed_envs..env.localnet.template.approved.txt rename to tests/project/bootstrap/test_bootstrap_env.test_bootstrap_network_prefixed_envs..env.localnet.template.approved.txt diff --git a/tests/bootstrap/test_bootstrap_env.test_bootstrap_network_prefixed_envs..env.template.approved.txt b/tests/project/bootstrap/test_bootstrap_env.test_bootstrap_network_prefixed_envs..env.template.approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_env.test_bootstrap_network_prefixed_envs..env.template.approved.txt rename to tests/project/bootstrap/test_bootstrap_env.test_bootstrap_network_prefixed_envs..env.template.approved.txt diff --git a/tests/bootstrap/test_bootstrap_npm.py b/tests/project/bootstrap/test_bootstrap_npm.py similarity index 93% rename from tests/bootstrap/test_bootstrap_npm.py rename to tests/project/bootstrap/test_bootstrap_npm.py index 5f8facd4..b7321d42 100644 --- a/tests/bootstrap/test_bootstrap_npm.py +++ b/tests/project/bootstrap/test_bootstrap_npm.py @@ -15,7 +15,7 @@ def test_bootstrap_npm_without_npm( (cwd / "package.json").touch() result = invoke( - "bootstrap npm", + "project bootstrap npm", cwd=cwd, ) @@ -27,7 +27,7 @@ def test_bootstrap_npm_without_npm( def test_bootstrap_npm_without_package_file(tmp_path_factory: TempPathFactory, request: pytest.FixtureRequest) -> None: cwd = tmp_path_factory.mktemp("cwd") result = invoke( - "bootstrap npm", + "project bootstrap npm", cwd=cwd, ) @@ -44,7 +44,7 @@ def test_bootstrap_npm_without_npm_and_package_file( cwd = tmp_path_factory.mktemp("cwd") result = invoke( - "bootstrap npm", + "project bootstrap npm", cwd=cwd, ) @@ -58,7 +58,7 @@ def test_bootstrap_npm_happy_path(tmp_path_factory: TempPathFactory, request: py (cwd / "package.json").touch() result = invoke( - "bootstrap npm", + "project bootstrap npm", cwd=cwd, ) diff --git a/tests/bootstrap/test_bootstrap_npm.test_bootstrap_npm_happy_path[linux].approved.txt b/tests/project/bootstrap/test_bootstrap_npm.test_bootstrap_npm_happy_path[linux].approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_npm.test_bootstrap_npm_happy_path[linux].approved.txt rename to tests/project/bootstrap/test_bootstrap_npm.test_bootstrap_npm_happy_path[linux].approved.txt diff --git a/tests/bootstrap/test_bootstrap_npm.test_bootstrap_npm_happy_path[macOS].approved.txt b/tests/project/bootstrap/test_bootstrap_npm.test_bootstrap_npm_happy_path[macOS].approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_npm.test_bootstrap_npm_happy_path[macOS].approved.txt rename to tests/project/bootstrap/test_bootstrap_npm.test_bootstrap_npm_happy_path[macOS].approved.txt diff --git a/tests/bootstrap/test_bootstrap_npm.test_bootstrap_npm_happy_path[windows].approved.txt b/tests/project/bootstrap/test_bootstrap_npm.test_bootstrap_npm_happy_path[windows].approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_npm.test_bootstrap_npm_happy_path[windows].approved.txt rename to tests/project/bootstrap/test_bootstrap_npm.test_bootstrap_npm_happy_path[windows].approved.txt diff --git a/tests/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_npm[linux].approved.txt b/tests/project/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_npm[linux].approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_npm[linux].approved.txt rename to tests/project/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_npm[linux].approved.txt diff --git a/tests/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_npm[macOS].approved.txt b/tests/project/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_npm[macOS].approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_npm[macOS].approved.txt rename to tests/project/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_npm[macOS].approved.txt diff --git a/tests/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_npm[windows].approved.txt b/tests/project/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_npm[windows].approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_npm[windows].approved.txt rename to tests/project/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_npm[windows].approved.txt diff --git a/tests/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_npm_and_package_file[linux].approved.txt b/tests/project/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_npm_and_package_file[linux].approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_npm_and_package_file[linux].approved.txt rename to tests/project/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_npm_and_package_file[linux].approved.txt diff --git a/tests/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_npm_and_package_file[macOS].approved.txt b/tests/project/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_npm_and_package_file[macOS].approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_npm_and_package_file[macOS].approved.txt rename to tests/project/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_npm_and_package_file[macOS].approved.txt diff --git a/tests/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_npm_and_package_file[windows].approved.txt b/tests/project/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_npm_and_package_file[windows].approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_npm_and_package_file[windows].approved.txt rename to tests/project/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_npm_and_package_file[windows].approved.txt diff --git a/tests/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_package_file[linux].approved.txt b/tests/project/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_package_file[linux].approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_package_file[linux].approved.txt rename to tests/project/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_package_file[linux].approved.txt diff --git a/tests/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_package_file[macOS].approved.txt b/tests/project/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_package_file[macOS].approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_package_file[macOS].approved.txt rename to tests/project/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_package_file[macOS].approved.txt diff --git a/tests/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_package_file[windows].approved.txt b/tests/project/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_package_file[windows].approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_package_file[windows].approved.txt rename to tests/project/bootstrap/test_bootstrap_npm.test_bootstrap_npm_without_package_file[windows].approved.txt diff --git a/tests/bootstrap/test_bootstrap_poetry.py b/tests/project/bootstrap/test_bootstrap_poetry.py similarity index 93% rename from tests/bootstrap/test_bootstrap_poetry.py rename to tests/project/bootstrap/test_bootstrap_poetry.py index 671c92e2..712e85a4 100644 --- a/tests/bootstrap/test_bootstrap_poetry.py +++ b/tests/project/bootstrap/test_bootstrap_poetry.py @@ -44,7 +44,7 @@ def test_base_python_path(python_base_executable: str) -> None: @pytest.mark.usefixtures("proc_mock") def test_bootstrap_poetry_with_poetry() -> None: - result = invoke("bootstrap poetry") + result = invoke("project bootstrap poetry") assert result.exit_code == 0 verify(result.output) @@ -55,7 +55,7 @@ def test_bootstrap_poetry_without_poetry(proc_mock: ProcMock, mock_questionary_i # Yes, install poetry mock_questionary_input.send_text("Y") - result = invoke("bootstrap poetry") + result = invoke("project bootstrap poetry") assert result.exit_code == 0 verify(result.output) @@ -67,7 +67,7 @@ def test_bootstrap_poetry_without_poetry_failed_install(proc_mock: ProcMock, moc # Yes, install poetry mock_questionary_input.send_text("Y") - result = invoke("bootstrap poetry") + result = invoke("project bootstrap poetry") assert result.exit_code == 1 verify(result.output) @@ -81,7 +81,7 @@ def test_bootstrap_poetry_without_poetry_failed_poetry_path( # Yes, install poetry mock_questionary_input.send_text("Y") - result = invoke("bootstrap poetry") + result = invoke("project bootstrap poetry") assert result.exit_code == 1 verify(result.output) @@ -109,7 +109,7 @@ def test_bootstrap_poetry_without_poetry_or_pipx_path( # Yes, install poetry mock_questionary_input.send_text("Y") - result = invoke("bootstrap poetry") + result = invoke("project bootstrap poetry") assert result.exit_code == 0 verify(result.output.replace(python_base_executable, "{python_base_executable}"), namer=PyTestNamer(request)) @@ -125,7 +125,7 @@ def test_bootstrap_poetry_without_poetry_or_pipx_path_failed_install( # Yes, install poetry mock_questionary_input.send_text("Y") - result = invoke("bootstrap poetry") + result = invoke("project bootstrap poetry") assert result.exit_code == 1 verify(result.output.replace(python_base_executable, "{python_base_executable}")) @@ -141,7 +141,7 @@ def test_bootstrap_poetry_without_poetry_or_pipx_path_failed_poetry_path( # Yes, install poetry mock_questionary_input.send_text("Y") - result = invoke("bootstrap poetry") + result = invoke("project bootstrap poetry") assert result.exit_code == 1 verify(result.output.replace(python_base_executable, "{python_base_executable}")) @@ -157,7 +157,7 @@ def test_bootstrap_poetry_without_poetry_or_pipx_path_or_pipx_module( # Yes, install poetry mock_questionary_input.send_text("Y") - result = invoke("bootstrap poetry") + result = invoke("project bootstrap poetry") assert result.exit_code == 1 verify(result.output.replace(python_base_executable, "{python_base_executable}")) diff --git a/tests/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_with_poetry.approved.txt b/tests/project/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_with_poetry.approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_with_poetry.approved.txt rename to tests/project/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_with_poetry.approved.txt diff --git a/tests/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry.approved.txt b/tests/project/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry.approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry.approved.txt rename to tests/project/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry.approved.txt diff --git a/tests/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_failed_install.approved.txt b/tests/project/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_failed_install.approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_failed_install.approved.txt rename to tests/project/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_failed_install.approved.txt diff --git a/tests/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_failed_poetry_path.approved.txt b/tests/project/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_failed_poetry_path.approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_failed_poetry_path.approved.txt rename to tests/project/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_failed_poetry_path.approved.txt diff --git a/tests/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path[no_system_pythons].approved.txt b/tests/project/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path[no_system_pythons].approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path[no_system_pythons].approved.txt rename to tests/project/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path[no_system_pythons].approved.txt diff --git a/tests/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path[python3_only].approved.txt b/tests/project/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path[python3_only].approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path[python3_only].approved.txt rename to tests/project/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path[python3_only].approved.txt diff --git a/tests/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path[python_and_python3].approved.txt b/tests/project/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path[python_and_python3].approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path[python_and_python3].approved.txt rename to tests/project/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path[python_and_python3].approved.txt diff --git a/tests/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path[python_only].approved.txt b/tests/project/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path[python_only].approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path[python_only].approved.txt rename to tests/project/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path[python_only].approved.txt diff --git a/tests/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path_failed_install.approved.txt b/tests/project/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path_failed_install.approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path_failed_install.approved.txt rename to tests/project/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path_failed_install.approved.txt diff --git a/tests/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path_failed_poetry_path.approved.txt b/tests/project/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path_failed_poetry_path.approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path_failed_poetry_path.approved.txt rename to tests/project/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path_failed_poetry_path.approved.txt diff --git a/tests/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path_or_pipx_module.approved.txt b/tests/project/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path_or_pipx_module.approved.txt similarity index 100% rename from tests/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path_or_pipx_module.approved.txt rename to tests/project/bootstrap/test_bootstrap_poetry.test_bootstrap_poetry_without_poetry_or_pipx_path_or_pipx_module.approved.txt diff --git a/tests/project/deploy/__init__.py b/tests/project/deploy/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/deploy/test_deploy.py b/tests/project/deploy/test_deploy.py similarity index 88% rename from tests/deploy/test_deploy.py rename to tests/project/deploy/test_deploy.py index ad573f76..379a1c8b 100644 --- a/tests/deploy/test_deploy.py +++ b/tests/project/deploy/test_deploy.py @@ -28,20 +28,20 @@ @pytest.fixture(autouse=True) def which_mock(mocker: MockerFixture) -> WhichMock: which_mock = WhichMock() - mocker.patch("algokit.core.deploy.shutil.which").side_effect = which_mock.which + mocker.patch("algokit.core.utils.shutil.which").side_effect = which_mock.which return which_mock def test_algokit_config_empty_array(tmp_path_factory: TempPathFactory) -> None: empty_array_config = """ -[deploy] +[project.deploy] command = [] """.strip() cwd = tmp_path_factory.mktemp("cwd") (cwd / ALGOKIT_CONFIG).write_text(empty_array_config, encoding="utf-8") (cwd / ".env").touch() - result = invoke(["deploy"], cwd=cwd) + result = invoke(["project", "deploy"], cwd=cwd) assert result.exit_code != 0 verify(result.output) @@ -55,7 +55,7 @@ def test_algokit_config_invalid_syntax(tmp_path_factory: TempPathFactory) -> Non cwd = tmp_path_factory.mktemp("cwd") (cwd / ALGOKIT_CONFIG).write_text(invalid_config, encoding="utf-8") (cwd / ".env").touch() - result = invoke(["deploy"], cwd=cwd) + result = invoke(["project", "deploy"], cwd=cwd) assert result.exit_code != 0 verify(result.output) @@ -65,13 +65,13 @@ def test_algokit_config_name_overrides( tmp_path_factory: TempPathFactory, proc_mock: ProcMock, which_mock: WhichMock ) -> None: config_with_override = """ -[deploy] +[project.deploy] command = "command_a" -[deploy.localnet] +[project.deploy.localnet] command = "command_b" -[deploy.testnet] +[project.deploy.testnet] command = "command_c" """.strip() cwd = tmp_path_factory.mktemp("cwd") @@ -83,7 +83,7 @@ def test_algokit_config_name_overrides( resolved_cmd = which_mock.add("command_c") proc_mock.set_output([resolved_cmd], ["picked testnet"]) - result = invoke(["deploy", "testnet"], cwd=cwd) + result = invoke(["project", "deploy", "testnet"], cwd=cwd) assert result.exit_code == 0 verify(result.output) @@ -93,10 +93,10 @@ def test_algokit_config_name_no_base( tmp_path_factory: TempPathFactory, proc_mock: ProcMock, which_mock: WhichMock ) -> None: config_with_override = """ -[deploy.localnet] +[project.deploy.localnet] command = "command_a" -[deploy.testnet] +[project.deploy.testnet] command = "command_b" """.strip() cwd = tmp_path_factory.mktemp("cwd") @@ -107,7 +107,7 @@ def test_algokit_config_name_no_base( cmd = which_mock.add("command_a") proc_mock.set_output([cmd], ["picked localnet"]) - result = invoke(["deploy", "localnet"], cwd=cwd) + result = invoke(["project", "deploy", "localnet"], cwd=cwd) assert result.exit_code == 0 verify(result.output) @@ -115,12 +115,13 @@ def test_algokit_config_name_no_base( def test_command_invocation_and_command_splitting(tmp_path: Path) -> None: config_data = """ -[deploy] +[project.deploy] command = ["not", "used"] """.strip() (tmp_path / ALGOKIT_CONFIG).write_text(config_data, encoding="utf-8") result = invoke( [ + "project", "deploy", "--command", f'{PYTHON_EXECUTABLE} -c "{TEST_PYTHON_COMMAND}"', @@ -133,22 +134,22 @@ def test_command_invocation_and_command_splitting(tmp_path: Path) -> None: def test_command_splitting_from_config(tmp_path: Path) -> None: config_data = rf""" -[deploy] +[project.deploy] command = "{PYTHON_EXECUTABLE_ESCAPED} -c \"{TEST_PYTHON_COMMAND}\"" """.strip() (tmp_path / ALGOKIT_CONFIG).write_text(config_data, encoding="utf-8") - result = invoke("deploy", cwd=tmp_path) + result = invoke(["project", "deploy"], cwd=tmp_path) assert result.exit_code == 0 verify(result.output.replace(PYTHON_EXECUTABLE, "")) def test_command_without_splitting_from_config(tmp_path: Path) -> None: config_data = rf""" -[deploy] +[project.deploy] command = ["{PYTHON_EXECUTABLE_ESCAPED}", "-c", "{TEST_PYTHON_COMMAND}"] """.strip() (tmp_path / ALGOKIT_CONFIG).write_text(config_data, encoding="utf-8") - result = invoke("deploy", cwd=tmp_path) + result = invoke(["project", "deploy"], cwd=tmp_path) assert result.exit_code == 0 verify(result.output.replace(PYTHON_EXECUTABLE, "")) @@ -156,7 +157,7 @@ def test_command_without_splitting_from_config(tmp_path: Path) -> None: @pytest.mark.usefixtures("proc_mock") def test_command_not_found_and_no_config(tmp_path: Path) -> None: cmd = "gm" - result = invoke(["deploy", "--command", cmd], cwd=tmp_path) + result = invoke(["project", "deploy", "--command", cmd], cwd=tmp_path) assert result.exit_code != 0 verify(result.output) @@ -165,7 +166,7 @@ def test_command_not_executable(proc_mock: ProcMock, tmp_path: Path, which_mock: cmd = "gm" cmd_resolved = which_mock.add(cmd) proc_mock.should_deny_on([cmd_resolved]) - result = invoke(["deploy", "--command", cmd], cwd=tmp_path) + result = invoke(["project", "deploy", "--command", cmd], cwd=tmp_path) assert result.exit_code != 0 verify(result.output) @@ -174,14 +175,14 @@ def test_command_bad_exit_code(proc_mock: ProcMock, tmp_path: Path, which_mock: cmd = "gm" cmd_resolved = which_mock.add(cmd) proc_mock.should_bad_exit_on([cmd_resolved], output=["it is not morning"]) - result = invoke(["deploy", "--command", cmd], cwd=tmp_path) + result = invoke(["project", "deploy", "--command", cmd], cwd=tmp_path) assert result.exit_code != 0 verify(result.output) def test_algokit_env_name_missing(tmp_path_factory: TempPathFactory, which_mock: WhichMock) -> None: config_with_override = """ -[deploy.localnet] +[project.deploy.localnet] command = "command_a" """.strip() cwd = tmp_path_factory.mktemp("cwd") @@ -189,7 +190,7 @@ def test_algokit_env_name_missing(tmp_path_factory: TempPathFactory, which_mock: (cwd / ".env").touch() which_mock.add("command_a") - result = invoke(["deploy", "localnet"], cwd=cwd) + result = invoke(["project", "deploy", "localnet"], cwd=cwd) assert result.exit_code == 1 verify(result.output) @@ -212,10 +213,10 @@ def test_algokit_env_and_name_correct_set( monkeypatch.setenv("ENV_A", "ENVIRON_ENV_A") config_with_deploy_name = """ -[deploy] +[project.deploy] command = "command_a" -[deploy.localnet] +[project.deploy.localnet] command = "command_b" """.strip() @@ -227,7 +228,7 @@ def test_algokit_env_and_name_correct_set( cmd_resolved = which_mock.add("command_b") proc_mock.set_output([cmd_resolved], ["picked localnet"]) - result = invoke(["deploy", "localnet"], cwd=cwd) + result = invoke(["project", "deploy", "localnet"], cwd=cwd) assert proc_mock.called[0].env passed_env_vars = proc_mock.called[0].env @@ -243,7 +244,7 @@ def test_algokit_deploy_only_base_deploy_config( tmp_path_factory: TempPathFactory, proc_mock: ProcMock, which_mock: WhichMock ) -> None: config_with_only_base_deploy = """ -[deploy] +[project.deploy] command = "command_a" """.strip() @@ -258,7 +259,7 @@ def test_algokit_deploy_only_base_deploy_config( cmd_resolved = which_mock.add("command_a") proc_mock.set_output([cmd_resolved], ["picked base deploy command"]) - result = invoke(["deploy"], cwd=cwd) + result = invoke(["project", "deploy"], cwd=cwd) assert result.exit_code == 0 assert proc_mock.called[0].env @@ -281,7 +282,7 @@ def test_ci_flag_interactivity_mode_via_env( mock_prompt = mocker.patch("click.prompt") config_with_only_base_deploy = """ -[deploy] +[project.deploy] command = "command_a" environment_secrets = [ "DEPLOYER_MNEMONIC" @@ -295,7 +296,7 @@ def test_ci_flag_interactivity_mode_via_env( cmd_resolved = which_mock.add("command_a") proc_mock.set_output([cmd_resolved], ["picked base deploy command"]) - result = invoke(["deploy"], cwd=cwd) + result = invoke(["project", "deploy"], cwd=cwd) mock_prompt.assert_not_called() assert result.exit_code != 0 @@ -312,7 +313,7 @@ def test_ci_flag_interactivity_mode_via_cli( mock_prompt = mocker.patch("click.prompt") config_with_only_base_deploy = """ -[deploy] +[project.deploy] command = "command_a" environment_secrets = [ "DEPLOYER_MNEMONIC" @@ -326,7 +327,7 @@ def test_ci_flag_interactivity_mode_via_cli( cmd_resolved = which_mock.add("command_a") proc_mock.set_output([cmd_resolved], ["picked base deploy command"]) - result = invoke(["deploy", "--ci"], cwd=cwd) + result = invoke(["project", "deploy", "--ci"], cwd=cwd) mock_prompt.assert_not_called() assert result.exit_code != 0 @@ -348,7 +349,7 @@ def test_secrets_prompting_via_stdin( # mock click.prompt mock_prompt = mocker.patch("click.prompt", return_value="secret_value") config_with_only_base_deploy = """ -[deploy] +[project.deploy] command = "command_a" environment_secrets = [ "DEPLOYER_MNEMONIC" @@ -361,7 +362,7 @@ def test_secrets_prompting_via_stdin( cmd_resolved = which_mock.add("command_a") proc_mock.set_output([cmd_resolved], ["picked base deploy command"]) - result = invoke(["deploy"], cwd=cwd) + result = invoke(["project", "deploy"], cwd=cwd) mock_prompt.assert_called_once() # ensure called assert result.exit_code == 0 # ensure success @@ -383,7 +384,7 @@ def test_deploy_custom_project_dir( custom_folder.mkdir() (custom_folder / ALGOKIT_CONFIG).write_text( """ -[deploy] +[project.deploy] command = "command_a" """.strip(), encoding="utf-8", @@ -397,7 +398,7 @@ def test_deploy_custom_project_dir( # Below is needed for escaping the backslash in the path on Windows # Works on Linux as well since \\ doesn't exist in the path in such cases path = str(custom_folder.absolute()).replace("\\", r"\\") - result = invoke(f"deploy testnet --path={path}", cwd=cwd, input="\n".join(input_answers)) + result = invoke(f"project deploy testnet --path={path}", cwd=cwd, input="\n".join(input_answers)) assert result.exit_code == 0 verify(result.output) @@ -408,14 +409,14 @@ def test_deploy_shutil_command_not_found(tmp_path_factory: TempPathFactory) -> N (cwd / ALGOKIT_CONFIG).write_text( """ -[deploy] +[project.deploy] command = "command_a" """.strip(), encoding="utf-8", ) (cwd / ".env").touch() - result = invoke("deploy", cwd=cwd) + result = invoke(["project", "deploy"], cwd=cwd) assert result.exit_code == 1 verify(result.output) @@ -444,7 +445,7 @@ def test_deploy_dispenser_alias( monkeypatch.setenv(env_var_name, "GENERIC_ENV_A") config_with_deploy_name = f""" -[deploy] +[project.deploy] command = "command_a" environment_secrets = [ "{env_var_name}" @@ -459,7 +460,7 @@ def test_deploy_dispenser_alias( (cwd / ALGOKIT_CONFIG).write_text(config_with_deploy_name, encoding="utf-8") (cwd / ".env").write_text(env_config, encoding="utf-8") which_mock.add("command_a") - result = invoke(["deploy", f"--{alias}", alias], cwd=cwd) + result = invoke(["project", "deploy", f"--{alias}", alias], cwd=cwd) assert proc_mock.called[0].env passed_env_vars = proc_mock.called[0].env diff --git a/tests/deploy/test_deploy.test_algokit_config_empty_array.approved.txt b/tests/project/deploy/test_deploy.test_algokit_config_empty_array.approved.txt similarity index 75% rename from tests/deploy/test_deploy.test_algokit_config_empty_array.approved.txt rename to tests/project/deploy/test_deploy.test_algokit_config_empty_array.approved.txt index 19b23e52..1495b327 100644 --- a/tests/deploy/test_deploy.test_algokit_config_empty_array.approved.txt +++ b/tests/project/deploy/test_deploy.test_algokit_config_empty_array.approved.txt @@ -1,3 +1,4 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: Deploying from project directory: {current_working_directory} DEBUG: Loading deploy command from project config DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml diff --git a/tests/deploy/test_deploy.test_algokit_config_invalid_syntax.approved.txt b/tests/project/deploy/test_deploy.test_algokit_config_invalid_syntax.approved.txt similarity index 67% rename from tests/deploy/test_deploy.test_algokit_config_invalid_syntax.approved.txt rename to tests/project/deploy/test_deploy.test_algokit_config_invalid_syntax.approved.txt index cda797f2..b654dce5 100644 --- a/tests/deploy/test_deploy.test_algokit_config_invalid_syntax.approved.txt +++ b/tests/project/deploy/test_deploy.test_algokit_config_invalid_syntax.approved.txt @@ -1,3 +1,5 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: Error parsing .algokit.toml file: Invalid statement (at line 1, column 1) DEBUG: Deploying from project directory: {current_working_directory} DEBUG: Loading deploy command from project config DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml diff --git a/tests/deploy/test_deploy.test_algokit_config_name_no_base.approved.txt b/tests/project/deploy/test_deploy.test_algokit_config_name_no_base.approved.txt similarity index 83% rename from tests/deploy/test_deploy.test_algokit_config_name_no_base.approved.txt rename to tests/project/deploy/test_deploy.test_algokit_config_name_no_base.approved.txt index 7c601a17..afde59af 100644 --- a/tests/deploy/test_deploy.test_algokit_config_name_no_base.approved.txt +++ b/tests/project/deploy/test_deploy.test_algokit_config_name_no_base.approved.txt @@ -1,3 +1,4 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: Deploying from project directory: {current_working_directory} DEBUG: Loading deploy command from project config DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml diff --git a/tests/deploy/test_deploy.test_algokit_config_name_overrides.approved.txt b/tests/project/deploy/test_deploy.test_algokit_config_name_overrides.approved.txt similarity index 83% rename from tests/deploy/test_deploy.test_algokit_config_name_overrides.approved.txt rename to tests/project/deploy/test_deploy.test_algokit_config_name_overrides.approved.txt index 1b15fee8..f03c314a 100644 --- a/tests/deploy/test_deploy.test_algokit_config_name_overrides.approved.txt +++ b/tests/project/deploy/test_deploy.test_algokit_config_name_overrides.approved.txt @@ -1,3 +1,4 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: Deploying from project directory: {current_working_directory} DEBUG: Loading deploy command from project config DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml diff --git a/tests/deploy/test_deploy.test_algokit_deploy_only_base_deploy_config.approved.txt b/tests/project/deploy/test_deploy.test_algokit_deploy_only_base_deploy_config.approved.txt similarity index 83% rename from tests/deploy/test_deploy.test_algokit_deploy_only_base_deploy_config.approved.txt rename to tests/project/deploy/test_deploy.test_algokit_deploy_only_base_deploy_config.approved.txt index 4f5c01c9..45dd697d 100644 --- a/tests/deploy/test_deploy.test_algokit_deploy_only_base_deploy_config.approved.txt +++ b/tests/project/deploy/test_deploy.test_algokit_deploy_only_base_deploy_config.approved.txt @@ -1,3 +1,4 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: Deploying from project directory: {current_working_directory} DEBUG: Loading deploy command from project config DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml diff --git a/tests/deploy/test_deploy.test_algokit_env_and_name_correct_set.approved.txt b/tests/project/deploy/test_deploy.test_algokit_env_and_name_correct_set.approved.txt similarity index 83% rename from tests/deploy/test_deploy.test_algokit_env_and_name_correct_set.approved.txt rename to tests/project/deploy/test_deploy.test_algokit_env_and_name_correct_set.approved.txt index 63c3a358..01699c49 100644 --- a/tests/deploy/test_deploy.test_algokit_env_and_name_correct_set.approved.txt +++ b/tests/project/deploy/test_deploy.test_algokit_env_and_name_correct_set.approved.txt @@ -1,3 +1,4 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: Deploying from project directory: {current_working_directory} DEBUG: Loading deploy command from project config DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml diff --git a/tests/deploy/test_deploy.test_algokit_env_name_missing.approved.txt b/tests/project/deploy/test_deploy.test_algokit_env_name_missing.approved.txt similarity index 79% rename from tests/deploy/test_deploy.test_algokit_env_name_missing.approved.txt rename to tests/project/deploy/test_deploy.test_algokit_env_name_missing.approved.txt index dc1d777d..15e20f40 100644 --- a/tests/deploy/test_deploy.test_algokit_env_name_missing.approved.txt +++ b/tests/project/deploy/test_deploy.test_algokit_env_name_missing.approved.txt @@ -1,3 +1,4 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: Deploying from project directory: {current_working_directory} DEBUG: Loading deploy command from project config DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml diff --git a/tests/deploy/test_deploy.test_ci_flag_interactivity_mode_via_cli.approved.txt b/tests/project/deploy/test_deploy.test_ci_flag_interactivity_mode_via_cli.approved.txt similarity index 79% rename from tests/deploy/test_deploy.test_ci_flag_interactivity_mode_via_cli.approved.txt rename to tests/project/deploy/test_deploy.test_ci_flag_interactivity_mode_via_cli.approved.txt index 6c537d26..295beb6d 100644 --- a/tests/deploy/test_deploy.test_ci_flag_interactivity_mode_via_cli.approved.txt +++ b/tests/project/deploy/test_deploy.test_ci_flag_interactivity_mode_via_cli.approved.txt @@ -1,3 +1,4 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: Deploying from project directory: {current_working_directory} DEBUG: Loading deploy command from project config DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml diff --git a/tests/deploy/test_deploy.test_ci_flag_interactivity_mode_via_env.approved.txt b/tests/project/deploy/test_deploy.test_ci_flag_interactivity_mode_via_env.approved.txt similarity index 79% rename from tests/deploy/test_deploy.test_ci_flag_interactivity_mode_via_env.approved.txt rename to tests/project/deploy/test_deploy.test_ci_flag_interactivity_mode_via_env.approved.txt index 6c537d26..295beb6d 100644 --- a/tests/deploy/test_deploy.test_ci_flag_interactivity_mode_via_env.approved.txt +++ b/tests/project/deploy/test_deploy.test_ci_flag_interactivity_mode_via_env.approved.txt @@ -1,3 +1,4 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: Deploying from project directory: {current_working_directory} DEBUG: Loading deploy command from project config DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml diff --git a/tests/deploy/test_deploy.test_command_bad_exit_code.approved.txt b/tests/project/deploy/test_deploy.test_command_bad_exit_code.approved.txt similarity index 78% rename from tests/deploy/test_deploy.test_command_bad_exit_code.approved.txt rename to tests/project/deploy/test_deploy.test_command_bad_exit_code.approved.txt index de50b39c..4c4a813d 100644 --- a/tests/deploy/test_deploy.test_command_bad_exit_code.approved.txt +++ b/tests/project/deploy/test_deploy.test_command_bad_exit_code.approved.txt @@ -1,3 +1,5 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: Deploying from project directory: {current_working_directory} DEBUG: Loading deploy command from project config DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml diff --git a/tests/deploy/test_deploy.test_command_invocation_and_command_splitting.approved.txt b/tests/project/deploy/test_deploy.test_command_invocation_and_command_splitting.approved.txt similarity index 85% rename from tests/deploy/test_deploy.test_command_invocation_and_command_splitting.approved.txt rename to tests/project/deploy/test_deploy.test_command_invocation_and_command_splitting.approved.txt index 58a77be3..c4d0b879 100644 --- a/tests/deploy/test_deploy.test_command_invocation_and_command_splitting.approved.txt +++ b/tests/project/deploy/test_deploy.test_command_invocation_and_command_splitting.approved.txt @@ -1,3 +1,4 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: Deploying from project directory: {current_working_directory} DEBUG: Loading deploy command from project config DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml diff --git a/tests/deploy/test_deploy.test_command_not_executable.approved.txt b/tests/project/deploy/test_deploy.test_command_not_executable.approved.txt similarity index 78% rename from tests/deploy/test_deploy.test_command_not_executable.approved.txt rename to tests/project/deploy/test_deploy.test_command_not_executable.approved.txt index 78ad523c..c2037a58 100644 --- a/tests/deploy/test_deploy.test_command_not_executable.approved.txt +++ b/tests/project/deploy/test_deploy.test_command_not_executable.approved.txt @@ -1,3 +1,5 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: Deploying from project directory: {current_working_directory} DEBUG: Loading deploy command from project config DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml diff --git a/tests/deploy/test_deploy.test_command_not_found_and_no_config.approved.txt b/tests/project/deploy/test_deploy.test_command_not_found_and_no_config.approved.txt similarity index 56% rename from tests/deploy/test_deploy.test_command_not_found_and_no_config.approved.txt rename to tests/project/deploy/test_deploy.test_command_not_found_and_no_config.approved.txt index f584bbb0..8a4f6aca 100644 --- a/tests/deploy/test_deploy.test_command_not_found_and_no_config.approved.txt +++ b/tests/project/deploy/test_deploy.test_command_not_found_and_no_config.approved.txt @@ -1,5 +1,7 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: Deploying from project directory: {current_working_directory} DEBUG: Loading deploy command from project config DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. -Error: Failed to resolve deploy command, 'gm' wasn't found +Error: Failed to resolve command path, 'gm' wasn't found diff --git a/tests/deploy/test_deploy.test_command_splitting_from_config.approved.txt b/tests/project/deploy/test_deploy.test_command_splitting_from_config.approved.txt similarity index 85% rename from tests/deploy/test_deploy.test_command_splitting_from_config.approved.txt rename to tests/project/deploy/test_deploy.test_command_splitting_from_config.approved.txt index 58a77be3..c4d0b879 100644 --- a/tests/deploy/test_deploy.test_command_splitting_from_config.approved.txt +++ b/tests/project/deploy/test_deploy.test_command_splitting_from_config.approved.txt @@ -1,3 +1,4 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: Deploying from project directory: {current_working_directory} DEBUG: Loading deploy command from project config DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml diff --git a/tests/deploy/test_deploy.test_command_without_splitting_from_config.approved.txt b/tests/project/deploy/test_deploy.test_command_without_splitting_from_config.approved.txt similarity index 85% rename from tests/deploy/test_deploy.test_command_without_splitting_from_config.approved.txt rename to tests/project/deploy/test_deploy.test_command_without_splitting_from_config.approved.txt index 58a77be3..c4d0b879 100644 --- a/tests/deploy/test_deploy.test_command_without_splitting_from_config.approved.txt +++ b/tests/project/deploy/test_deploy.test_command_without_splitting_from_config.approved.txt @@ -1,3 +1,4 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: Deploying from project directory: {current_working_directory} DEBUG: Loading deploy command from project config DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml diff --git a/tests/deploy/test_deploy.test_deploy_custom_project_dir.approved.txt b/tests/project/deploy/test_deploy.test_deploy_custom_project_dir.approved.txt similarity index 77% rename from tests/deploy/test_deploy.test_deploy_custom_project_dir.approved.txt rename to tests/project/deploy/test_deploy.test_deploy_custom_project_dir.approved.txt index 737d30d3..50c32bf8 100644 --- a/tests/deploy/test_deploy.test_deploy_custom_project_dir.approved.txt +++ b/tests/project/deploy/test_deploy.test_deploy_custom_project_dir.approved.txt @@ -1,3 +1,5 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. DEBUG: Deploying from project directory: {current_working_directory}/custom_folder DEBUG: Loading deploy command from project config DEBUG: Attempting to load project config from {current_working_directory}/custom_folder/.algokit.toml diff --git a/tests/deploy/test_deploy.test_deploy_dispenser_alias.deployer.approved.txt b/tests/project/deploy/test_deploy.test_deploy_dispenser_alias.deployer.approved.txt similarity index 86% rename from tests/deploy/test_deploy.test_deploy_dispenser_alias.deployer.approved.txt rename to tests/project/deploy/test_deploy.test_deploy_dispenser_alias.deployer.approved.txt index 92e9cbfd..08270d6a 100644 --- a/tests/deploy/test_deploy.test_deploy_dispenser_alias.deployer.approved.txt +++ b/tests/project/deploy/test_deploy.test_deploy_dispenser_alias.deployer.approved.txt @@ -1,3 +1,4 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: Deploying from project directory: {current_working_directory} DEBUG: Loading deploy command from project config DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml diff --git a/tests/deploy/test_deploy.test_deploy_dispenser_alias.dispenser.approved.txt b/tests/project/deploy/test_deploy.test_deploy_dispenser_alias.dispenser.approved.txt similarity index 86% rename from tests/deploy/test_deploy.test_deploy_dispenser_alias.dispenser.approved.txt rename to tests/project/deploy/test_deploy.test_deploy_dispenser_alias.dispenser.approved.txt index da4125d2..0bd44db0 100644 --- a/tests/deploy/test_deploy.test_deploy_dispenser_alias.dispenser.approved.txt +++ b/tests/project/deploy/test_deploy.test_deploy_dispenser_alias.dispenser.approved.txt @@ -1,3 +1,4 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: Deploying from project directory: {current_working_directory} DEBUG: Loading deploy command from project config DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml diff --git a/tests/deploy/test_deploy.test_deploy_shutil_command_not_found.approved.txt b/tests/project/deploy/test_deploy.test_deploy_shutil_command_not_found.approved.txt similarity index 57% rename from tests/deploy/test_deploy.test_deploy_shutil_command_not_found.approved.txt rename to tests/project/deploy/test_deploy.test_deploy_shutil_command_not_found.approved.txt index 402ba633..b3c6a6b7 100644 --- a/tests/deploy/test_deploy.test_deploy_shutil_command_not_found.approved.txt +++ b/tests/project/deploy/test_deploy.test_deploy_shutil_command_not_found.approved.txt @@ -1,4 +1,5 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: Deploying from project directory: {current_working_directory} DEBUG: Loading deploy command from project config DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml -Error: Failed to resolve deploy command, 'command_a' wasn't found +Error: Failed to resolve command path, 'command_a' wasn't found diff --git a/tests/deploy/test_deploy.test_deploy_windows_command_not_found.approved.txt b/tests/project/deploy/test_deploy.test_deploy_windows_command_not_found.approved.txt similarity index 100% rename from tests/deploy/test_deploy.test_deploy_windows_command_not_found.approved.txt rename to tests/project/deploy/test_deploy.test_deploy_windows_command_not_found.approved.txt diff --git a/tests/deploy/test_deploy.test_secrets_prompting_via_stdin.approved.txt b/tests/project/deploy/test_deploy.test_secrets_prompting_via_stdin.approved.txt similarity index 83% rename from tests/deploy/test_deploy.test_secrets_prompting_via_stdin.approved.txt rename to tests/project/deploy/test_deploy.test_secrets_prompting_via_stdin.approved.txt index 4f5c01c9..45dd697d 100644 --- a/tests/deploy/test_deploy.test_secrets_prompting_via_stdin.approved.txt +++ b/tests/project/deploy/test_deploy.test_secrets_prompting_via_stdin.approved.txt @@ -1,3 +1,4 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: Deploying from project directory: {current_working_directory} DEBUG: Loading deploy command from project config DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml diff --git a/tests/project/link/application.json b/tests/project/link/application.json new file mode 100644 index 00000000..1c0952e7 --- /dev/null +++ b/tests/project/link/application.json @@ -0,0 +1,75 @@ +{ + "hints": { + "hello(string)string": { + "call_config": { + "no_op": "CALL" + } + }, + "hello_world_check(string)void": { + "call_config": { + "no_op": "CALL" + } + } + }, + "source": { + "approval": "I3ByYWdtYSB2ZXJzaW9uIDgKaW50Y2Jsb2NrIDAgMQp0eG4gTnVtQXBwQXJncwppbnRjXzAgLy8gMAo9PQpibnogbWFpbl9sNgp0eG5hIEFwcGxpY2F0aW9uQXJncyAwCnB1c2hieXRlcyAweDAyYmVjZTExIC8vICJoZWxsbyhzdHJpbmcpc3RyaW5nIgo9PQpibnogbWFpbl9sNQp0eG5hIEFwcGxpY2F0aW9uQXJncyAwCnB1c2hieXRlcyAweGJmOWMxZWRmIC8vICJoZWxsb193b3JsZF9jaGVjayhzdHJpbmcpdm9pZCIKPT0KYm56IG1haW5fbDQKZXJyCm1haW5fbDQ6CnR4biBPbkNvbXBsZXRpb24KaW50Y18wIC8vIE5vT3AKPT0KdHhuIEFwcGxpY2F0aW9uSUQKaW50Y18wIC8vIDAKIT0KJiYKYXNzZXJ0CnR4bmEgQXBwbGljYXRpb25BcmdzIDEKY2FsbHN1YiBoZWxsb3dvcmxkY2hlY2tfMwppbnRjXzEgLy8gMQpyZXR1cm4KbWFpbl9sNToKdHhuIE9uQ29tcGxldGlvbgppbnRjXzAgLy8gTm9PcAo9PQp0eG4gQXBwbGljYXRpb25JRAppbnRjXzAgLy8gMAohPQomJgphc3NlcnQKdHhuYSBBcHBsaWNhdGlvbkFyZ3MgMQpjYWxsc3ViIGhlbGxvXzIKc3RvcmUgMApwdXNoYnl0ZXMgMHgxNTFmN2M3NSAvLyAweDE1MWY3Yzc1CmxvYWQgMApjb25jYXQKbG9nCmludGNfMSAvLyAxCnJldHVybgptYWluX2w2Ogp0eG4gT25Db21wbGV0aW9uCmludGNfMCAvLyBOb09wCj09CmJueiBtYWluX2wxMgp0eG4gT25Db21wbGV0aW9uCnB1c2hpbnQgNCAvLyBVcGRhdGVBcHBsaWNhdGlvbgo9PQpibnogbWFpbl9sMTEKdHhuIE9uQ29tcGxldGlvbgpwdXNoaW50IDUgLy8gRGVsZXRlQXBwbGljYXRpb24KPT0KYm56IG1haW5fbDEwCmVycgptYWluX2wxMDoKdHhuIEFwcGxpY2F0aW9uSUQKaW50Y18wIC8vIDAKIT0KYXNzZXJ0CmNhbGxzdWIgZGVsZXRlXzEKaW50Y18xIC8vIDEKcmV0dXJuCm1haW5fbDExOgp0eG4gQXBwbGljYXRpb25JRAppbnRjXzAgLy8gMAohPQphc3NlcnQKY2FsbHN1YiB1cGRhdGVfMAppbnRjXzEgLy8gMQpyZXR1cm4KbWFpbl9sMTI6CnR4biBBcHBsaWNhdGlvbklECmludGNfMCAvLyAwCj09CmFzc2VydAppbnRjXzEgLy8gMQpyZXR1cm4KCi8vIHVwZGF0ZQp1cGRhdGVfMDoKcHJvdG8gMCAwCnR4biBTZW5kZXIKZ2xvYmFsIENyZWF0b3JBZGRyZXNzCj09Ci8vIHVuYXV0aG9yaXplZAphc3NlcnQKcHVzaGludCBUTVBMX1VQREFUQUJMRSAvLyBUTVBMX1VQREFUQUJMRQovLyBDaGVjayBhcHAgaXMgdXBkYXRhYmxlCmFzc2VydApyZXRzdWIKCi8vIGRlbGV0ZQpkZWxldGVfMToKcHJvdG8gMCAwCnR4biBTZW5kZXIKZ2xvYmFsIENyZWF0b3JBZGRyZXNzCj09Ci8vIHVuYXV0aG9yaXplZAphc3NlcnQKcHVzaGludCBUTVBMX0RFTEVUQUJMRSAvLyBUTVBMX0RFTEVUQUJMRQovLyBDaGVjayBhcHAgaXMgZGVsZXRhYmxlCmFzc2VydApyZXRzdWIKCi8vIGhlbGxvCmhlbGxvXzI6CnByb3RvIDEgMQpwdXNoYnl0ZXMgMHggLy8gIiIKcHVzaGJ5dGVzIDB4NDg2NTZjNmM2ZjJjMjAgLy8gIkhlbGxvLCAiCmZyYW1lX2RpZyAtMQpleHRyYWN0IDIgMApjb25jYXQKZnJhbWVfYnVyeSAwCmZyYW1lX2RpZyAwCmxlbgppdG9iCmV4dHJhY3QgNiAwCmZyYW1lX2RpZyAwCmNvbmNhdApmcmFtZV9idXJ5IDAKcmV0c3ViCgovLyBoZWxsb193b3JsZF9jaGVjawpoZWxsb3dvcmxkY2hlY2tfMzoKcHJvdG8gMSAwCmZyYW1lX2RpZyAtMQpleHRyYWN0IDIgMApwdXNoYnl0ZXMgMHg1NzZmNzI2YzY0IC8vICJXb3JsZCIKPT0KYXNzZXJ0CnJldHN1Yg==", + "clear": "I3ByYWdtYSB2ZXJzaW9uIDgKcHVzaGludCAwIC8vIDAKcmV0dXJu" + }, + "state": { + "global": { + "num_byte_slices": 0, + "num_uints": 0 + }, + "local": { + "num_byte_slices": 0, + "num_uints": 0 + } + }, + "schema": { + "global": { + "declared": {}, + "reserved": {} + }, + "local": { + "declared": {}, + "reserved": {} + } + }, + "contract": { + "name": "HelloWorldApp", + "methods": [ + { + "name": "hello", + "args": [ + { + "type": "string", + "name": "name" + } + ], + "returns": { + "type": "string" + }, + "desc": "Returns Hello, {name}" + }, + { + "name": "hello_world_check", + "args": [ + { + "type": "string", + "name": "name" + } + ], + "returns": { + "type": "void" + }, + "desc": "Asserts {name} is \"World\"" + } + ], + "networks": {} + }, + "bare_call_config": { + "delete_application": "CALL", + "no_op": "CREATE", + "update_application": "CALL" + } +} \ No newline at end of file diff --git a/tests/project/link/test_link.py b/tests/project/link/test_link.py new file mode 100644 index 00000000..df515137 --- /dev/null +++ b/tests/project/link/test_link.py @@ -0,0 +1,279 @@ +import shutil +from pathlib import Path +from unittest.mock import MagicMock + +import pytest +from _pytest.tmpdir import TempPathFactory +from pytest_mock import MockerFixture + +from tests.utils.approvals import verify +from tests.utils.click_invoker import invoke +from tests.utils.proc_mock import ProcMock +from tests.utils.which_mock import WhichMock + + +@pytest.fixture() +def which_mock(mocker: MockerFixture) -> WhichMock: + """ + Fixture to mock 'shutil.which' with predefined responses. + """ + which_mock = WhichMock() + which_mock.add("npx") + mocker.patch("algokit.core.utils.shutil.which").side_effect = which_mock.which + return which_mock + + +@pytest.fixture(autouse=True) +def client_generator_mock(mocker: MockerFixture) -> MagicMock: + """ + Fixture to mock 'shutil.which' with predefined responses. + """ + + client_gen_mock = MagicMock() + mocker.patch("src.algokit.cli.generate.ClientGenerator.create_for_language", return_value=client_gen_mock) + return client_gen_mock + + +def _format_output(output: str, replacements: list[tuple[str, str]]) -> str: + """ + Modifies the output by replacing specified strings based on provided replacements. + Each replacement is a tuple where the first element is the target string to find, + and the second element is the string to replace it with. This function also ensures + that lines starting with "DEBUG" are fully removed from the output. + """ + for old, new in replacements: + output = output.replace(old, new) + output = output.replace("\\", "/") + return "\n".join([line for line in output.split("\n") if not line.startswith("DEBUG")]) + + +def _create_project_config( + project_dir: Path, + project_type: str, + project_name: str, + command: str, + description: str, + with_app_spec: bool = False, # noqa: FBT001, FBT002 +) -> None: + """ + Generates .algokit.toml configuration file in project directory. + """ + project_config = f""" +[project] +type = '{project_type}' +name = '{project_name}' +artifacts = 'dist' + +[project.run] +hello = {{ commands = ['{command}'], description = '{description}' }} + """.strip() + (project_dir / ".algokit.toml").write_text(project_config, encoding="utf-8") + + if project_type == "contract": + (project_dir / "dist").mkdir() + if with_app_spec: + app_spec_example_path = Path(__file__).parent / "application.json" + shutil.copy(app_spec_example_path, project_dir / "dist" / "application.json") + + +def _create_workspace_project( + *, + workspace_dir: Path, + projects: list[dict[str, str]], + mock_command: bool = False, + which_mock: WhichMock | None = None, + proc_mock: ProcMock | None = None, + custom_project_order: list[str] | None = None, + with_app_spec: bool = True, +) -> None: + """ + Sets up a workspace and its subprojects. + """ + workspace_dir.mkdir() + custom_project_order = custom_project_order if custom_project_order else ["contract_project", "frontend_project"] + (workspace_dir / ".algokit.toml").write_text( + f""" +[project] +type = 'workspace' +projects_root_path = 'projects' + +[project.run] +hello = {custom_project_order} + """.strip(), + encoding="utf-8", + ) + (workspace_dir / "projects").mkdir() + for project in projects: + project_dir = workspace_dir / "projects" / project["dir"] + project_dir.mkdir() + if mock_command and proc_mock and which_mock: + resolved_mocked_cmd = which_mock.add(project["command"]) + proc_mock.set_output([resolved_mocked_cmd], ["picked " + project["command"]]) + + _create_project_config( + project_dir, + project["type"], + project["name"], + project["command"], + project["description"], + with_app_spec=with_app_spec, + ) + + +def _cwd_with_workspace( + tmp_path_factory: TempPathFactory, + which_mock: WhichMock, + proc_mock: ProcMock, + num_projects: int = 1, + with_app_spec: bool = True, # noqa: FBT002, FBT001 +) -> Path: + """ + Generates a workspace with specified number of projects. + """ + + def _generate_projects(num: int) -> list[dict[str, str]]: + return [ + { + "dir": f"project{i+1}", + "type": "frontend" if i == 0 else "contract", + "name": f"contract_project_{i+1}", + "command": f"command_{chr(97+i)}", + "description": "Prints hello", + } + for i in range(num) + ] + + cwd = tmp_path_factory.mktemp("cwd") / "algokit_project" + projects = _generate_projects(num_projects) + _create_workspace_project( + workspace_dir=cwd, + projects=projects, + mock_command=True, + which_mock=which_mock, + proc_mock=proc_mock, + with_app_spec=with_app_spec, + ) + + return cwd + + +def test_link_command_by_name_success( + tmp_path_factory: TempPathFactory, which_mock: WhichMock, proc_mock: ProcMock, client_generator_mock: MagicMock +) -> None: + """ + Verifies 'project list' command success for a specific project name. + """ + cwd_with_workspace = _cwd_with_workspace(tmp_path_factory, which_mock, proc_mock, num_projects=5) + result = invoke("project link --project-name contract_project_3", cwd=cwd_with_workspace / "projects" / "project1") + + assert result.exit_code == 0 + client_generator_mock.resolve_output_path.assert_called_once() + client_generator_mock.generate.assert_called_once() + verify(_format_output(result.output, [(str(cwd_with_workspace), "")])) + + +def test_link_command_all_success( + tmp_path_factory: TempPathFactory, which_mock: WhichMock, proc_mock: ProcMock, client_generator_mock: MagicMock +) -> None: + """ + Confirms 'project list' command lists all projects successfully. + """ + contract_projects_count = 4 + frontend_projects_count = 1 + cwd_with_workspace = _cwd_with_workspace( + tmp_path_factory, which_mock, proc_mock, num_projects=contract_projects_count + frontend_projects_count + ) + result = invoke("project link --all", cwd=cwd_with_workspace / "projects" / "project1") + + assert result.exit_code == 0 + assert client_generator_mock.resolve_output_path.call_count == contract_projects_count + assert client_generator_mock.generate.call_count == contract_projects_count + verify(_format_output(result.output, [(str(cwd_with_workspace), "")])) + + +def test_link_command_multiple_names_success( + tmp_path_factory: TempPathFactory, which_mock: WhichMock, proc_mock: ProcMock, client_generator_mock: MagicMock +) -> None: + """ + Ensures 'project list' command success for multiple specified project names. + """ + projects_count = 5 + cwd_with_workspace = _cwd_with_workspace(tmp_path_factory, which_mock, proc_mock, num_projects=projects_count) + result = invoke( + "project link --project-name contract_project_3 --project-name contract_project_5", + cwd=cwd_with_workspace / "projects" / "project1", + ) + + assert result.exit_code == 0 + + expected_call_count = 2 + assert client_generator_mock.resolve_output_path.call_count == expected_call_count + assert client_generator_mock.generate.call_count == expected_call_count + verify(_format_output(result.output, [(str(cwd_with_workspace), "")])) + + +def test_link_command_multiple_names_no_specs_success( + tmp_path_factory: TempPathFactory, which_mock: WhichMock, proc_mock: ProcMock, client_generator_mock: MagicMock +) -> None: + """ + Ensures 'project list' command success for multiple specified project names. + """ + cwd_with_workspace = _cwd_with_workspace( + tmp_path_factory, which_mock, proc_mock, num_projects=5, with_app_spec=False + ) + result = invoke( + "project link --project-name contract_project_3 --project-name contract_project_5", + cwd=cwd_with_workspace / "projects" / "project1", + ) + + assert result.exit_code == 0 + assert client_generator_mock.resolve_output_path.call_count == 0 + assert client_generator_mock.generate.call_count == 0 + verify(_format_output(result.output, [(str(cwd_with_workspace), "")])) + + +def test_link_command_name_not_found( + tmp_path_factory: TempPathFactory, + which_mock: WhichMock, + proc_mock: ProcMock, +) -> None: + """ + Test to ensure the 'project list' command executes successfully within a workspace containing multiple projects. + + This test simulates a workspace environment with 20 projects and verifies that the + command lists all projects without errors. + + Args: + tmp_path_factory (TempPathFactory): A fixture to create temporary directories. + which_mock (WhichMock): A mock for the 'which' command. + proc_mock (ProcMock): A mock for process execution. + """ + cwd_with_workspace = _cwd_with_workspace(tmp_path_factory, which_mock, proc_mock, num_projects=5) + result = invoke( + "project link --project-name contract_project_13", + cwd=cwd_with_workspace / "projects" / "project1", + ) + + assert result.exit_code == 0 + verify(_format_output(result.output, [(str(cwd_with_workspace), "")])) + + +def test_link_command_empty_folder( + tmp_path_factory: TempPathFactory, +) -> None: + """ + Test to ensure the 'project list' command executes successfully within a workspace containing multiple projects. + + This test simulates a workspace environment with 20 projects and verifies that the + command lists all projects without errors. + + Args: + tmp_path_factory (TempPathFactory): A fixture to create temporary directories. + which_mock (WhichMock): A mock for the 'which' command. + proc_mock (ProcMock): A mock for process execution. + """ + cwd = tmp_path_factory.mktemp("cwd") + result = invoke("project link --all", cwd=cwd) + + assert result.exit_code == 0 + verify(_format_output(result.output, [(str(cwd), "")])) diff --git a/tests/project/link/test_link.test_link_command_all_success.approved.txt b/tests/project/link/test_link.test_link_command_all_success.approved.txt new file mode 100644 index 00000000..6dc5f867 --- /dev/null +++ b/tests/project/link/test_link.test_link_command_all_success.approved.txt @@ -0,0 +1,4 @@ +✅ 1/4: Finished processing contract_project_2 +✅ 2/4: Finished processing contract_project_3 +✅ 3/4: Finished processing contract_project_4 +✅ 4/4: Finished processing contract_project_5 diff --git a/tests/project/link/test_link.test_link_command_by_name_success.approved.txt b/tests/project/link/test_link.test_link_command_by_name_success.approved.txt new file mode 100644 index 00000000..be6f8ffe --- /dev/null +++ b/tests/project/link/test_link.test_link_command_by_name_success.approved.txt @@ -0,0 +1 @@ +✅ 1/1: Finished processing contract_project_3 diff --git a/tests/project/link/test_link.test_link_command_empty_folder.approved.txt b/tests/project/link/test_link.test_link_command_empty_folder.approved.txt new file mode 100644 index 00000000..8c894cca --- /dev/null +++ b/tests/project/link/test_link.test_link_command_empty_folder.approved.txt @@ -0,0 +1 @@ +WARNING: No .algokit.toml config found. Skipping... diff --git a/tests/project/link/test_link.test_link_command_multiple_names_no_specs_success.approved.txt b/tests/project/link/test_link.test_link_command_multiple_names_no_specs_success.approved.txt new file mode 100644 index 00000000..02c4f368 --- /dev/null +++ b/tests/project/link/test_link.test_link_command_multiple_names_no_specs_success.approved.txt @@ -0,0 +1,4 @@ +WARNING: No application.json | *.arc32.json files found in /projects/project3. Skipping... +✅ 1/2: Finished processing contract_project_3 +WARNING: No application.json | *.arc32.json files found in /projects/project5. Skipping... +✅ 2/2: Finished processing contract_project_5 diff --git a/tests/project/link/test_link.test_link_command_multiple_names_success.approved.txt b/tests/project/link/test_link.test_link_command_multiple_names_success.approved.txt new file mode 100644 index 00000000..8859a918 --- /dev/null +++ b/tests/project/link/test_link.test_link_command_multiple_names_success.approved.txt @@ -0,0 +1,2 @@ +✅ 1/2: Finished processing contract_project_3 +✅ 2/2: Finished processing contract_project_5 diff --git a/tests/project/link/test_link.test_link_command_name_not_found.approved.txt b/tests/project/link/test_link.test_link_command_name_not_found.approved.txt new file mode 100644 index 00000000..65d27916 --- /dev/null +++ b/tests/project/link/test_link.test_link_command_name_not_found.approved.txt @@ -0,0 +1 @@ +WARNING: No contract_project_13 found. Skipping... diff --git a/tests/project/link/test_link.test_link_runtime_error.approved.txt b/tests/project/link/test_link.test_link_runtime_error.approved.txt new file mode 100644 index 00000000..2d25d730 --- /dev/null +++ b/tests/project/link/test_link.test_link_runtime_error.approved.txt @@ -0,0 +1,2 @@ +ERROR: Couldn't parse contract name from /private/var/folders/t6/57q65mk543l7xw6_bdgx1bmc0000gn/T/pytest-of-aorumbayev/pytest-353/cwd5/algokit_project/projects/project3/dist/application.json +Error: Couldn't parse contract name from /private/var/folders/t6/57q65mk543l7xw6_bdgx1bmc0000gn/T/pytest-of-aorumbayev/pytest-353/cwd5/algokit_project/projects/project3/dist/application.json diff --git a/tests/project/link/test_link.test_list_command_from_workspace_success.approved.txt b/tests/project/link/test_link.test_list_command_from_workspace_success.approved.txt new file mode 100644 index 00000000..bd551322 --- /dev/null +++ b/tests/project/link/test_link.test_list_command_from_workspace_success.approved.txt @@ -0,0 +1 @@ +✅ 1/1: Exported typed clients from contract_project_3 typed clients to dist diff --git a/tests/project/list/test_list.py b/tests/project/list/test_list.py new file mode 100644 index 00000000..0c723596 --- /dev/null +++ b/tests/project/list/test_list.py @@ -0,0 +1,203 @@ +from pathlib import Path + +import pytest +from _pytest.tmpdir import TempPathFactory +from pytest_mock import MockerFixture + +from tests.utils.approvals import verify +from tests.utils.click_invoker import invoke +from tests.utils.proc_mock import ProcMock +from tests.utils.which_mock import WhichMock + + +@pytest.fixture() +def which_mock(mocker: MockerFixture) -> WhichMock: + which_mock = WhichMock() + mocker.patch("algokit.core.utils.shutil.which").side_effect = which_mock.which + return which_mock + + +def _format_output(output: str, replacements: list[tuple[str, str]], remove_debug: bool = True) -> str: # noqa: FBT002, FBT001 + """ + Modifies the output by replacing specified strings based on provided replacements. + Each replacement is a tuple where the first element is the target string to find, + and the second element is the string to replace it with. This function also ensures + that lines starting with "DEBUG" are fully removed from the output. + """ + for old, new in replacements: + output = output.replace(old, new) + return "\n".join([line for line in output.split("\n") if not (remove_debug and line.startswith("DEBUG"))]).replace( + "\\", r"/" + ) + + +def _create_project_config( + project_dir: Path, project_type: str, project_name: str, command: str, description: str +) -> None: + """ + Creates a .algokit.toml configuration file in the specified project directory. + + Args: + project_dir (Path): The directory of the project. + project_type (str): The type of the project. + project_name (str): The name of the project. + command (str): The command associated with the project. + description (str): A description of the project. + """ + project_config = f""" +[project] +type = '{project_type}' +name = '{project_name}' + +[project.run] +hello = {{ commands = ['{command}'], description = '{description}' }} + """.strip() + (project_dir / ".algokit.toml").write_text(project_config, encoding="utf-8") + + +def _create_workspace_project( + *, + workspace_dir: Path, + projects: list[dict[str, str]], + mock_command: bool = False, + which_mock: WhichMock | None = None, + proc_mock: ProcMock | None = None, + custom_project_order: list[str] | None = None, +) -> None: + """ + Creates a workspace project and its subprojects within the specified directory. + + Args: + workspace_dir (Path): The directory of the workspace. + projects (list[dict[str, str]]): A list of dictionaries, each representing a project with + keys for directory, type, name, command, and description. + mock_command (bool, optional): Indicates whether to mock the command. Defaults to False. + which_mock (WhichMock | None, optional): The mock object for the 'which' command. Defaults to None. + proc_mock (ProcMock | None, optional): The mock object for the process execution. Defaults to None. + custom_project_order (list[str] | None, optional): Specifies a custom order for project execution. + Defaults to None. + """ + workspace_dir.mkdir() + custom_project_order = custom_project_order if custom_project_order else ["contract_project", "frontend_project"] + (workspace_dir / ".algokit.toml").write_text( + f""" +[project] +type = 'workspace' +projects_root_path = 'projects' + +[project.run] +hello = {custom_project_order} + """.strip(), + encoding="utf-8", + ) + (workspace_dir / "projects").mkdir() + for project in projects: + project_dir = workspace_dir / "projects" / project["dir"] + project_dir.mkdir() + if mock_command and proc_mock and which_mock: + resolved_mocked_cmd = which_mock.add(project["command"]) + proc_mock.set_output([resolved_mocked_cmd], ["picked " + project["command"]]) + + _create_project_config( + project_dir, project["type"], project["name"], project["command"], project["description"] + ) + + +def _cwd_with_workspace( + tmp_path_factory: TempPathFactory, which_mock: WhichMock, proc_mock: ProcMock, num_projects: int = 1 +) -> Path: + """ + Creates a workspace with a specified number of standalone projects, each with a single command. + Projects are generated in a loop based on the number specified. + """ + + def _generate_projects(num: int) -> list[dict[str, str]]: + return [ + { + "dir": f"project{i+1}", + "type": "contract", + "name": f"contract_project_{i+1}", + "command": f"command_{chr(97+i)}", + "description": "Prints hello", + } + for i in range(num) + ] + + cwd = tmp_path_factory.mktemp("cwd") / "algokit_project" + projects = _generate_projects(num_projects) + _create_workspace_project( + workspace_dir=cwd, projects=projects, mock_command=True, which_mock=which_mock, proc_mock=proc_mock + ) + + return cwd + + +def test_list_command_from_workspace_success( + tmp_path_factory: TempPathFactory, which_mock: WhichMock, proc_mock: ProcMock +) -> None: + """ + Test to ensure the 'project list' command executes successfully within a workspace containing multiple projects. + + This test simulates a workspace environment with 20 projects and verifies that the + command lists all projects without errors. + + Args: + tmp_path_factory (TempPathFactory): A fixture to create temporary directories. + which_mock (WhichMock): A mock for the 'which' command. + proc_mock (ProcMock): A mock for process execution. + """ + cwd_with_workspace = _cwd_with_workspace(tmp_path_factory, which_mock, proc_mock, num_projects=20) + result = invoke(f"project list {cwd_with_workspace}".split(), cwd=cwd_with_workspace) + + assert result.exit_code == 0 + verify(_format_output(result.output, [(str(cwd_with_workspace), "")])) + + +def test_list_command_from_empty_folder( + tmp_path_factory: TempPathFactory, +) -> None: + """ + Test to verify that the 'project list' command executes successfully in an empty directory. + + This test ensures that executing the command in a directory without any projects or workspace + configuration does not result in errors. + + Args: + tmp_path_factory (TempPathFactory): A fixture to create temporary directories. + """ + empty_cwd = tmp_path_factory.mktemp("cwd") + result = invoke(f"project list {empty_cwd}".split(), cwd=empty_cwd) + + assert result.exit_code == 0 + verify( + _format_output( + result.output, + [(str(empty_cwd.parent), ""), (str(empty_cwd.parent.parent), "")], + remove_debug=False, + ) + ) + + +def test_list_command_no_args( + tmp_path_factory: TempPathFactory, +) -> None: + """ + Test to ensure the 'project list' command executes successfully without specifying a directory. + + This test checks that the command can be executed in an empty directory without passing any + arguments, and it completes without errors. + + Args: + tmp_path_factory (TempPathFactory): A fixture to create temporary directories. + """ + empty_cwd = tmp_path_factory.mktemp("cwd") + result = invoke("project list", cwd=empty_cwd) + + assert result.exit_code == 0 + verify( + _format_output( + result.output, + [(str(empty_cwd.parent), ""), (str(empty_cwd.parent.parent), "")], + remove_debug=False, + ) + ) diff --git a/tests/project/list/test_list.test_list_command_from_empty_folder.approved.txt b/tests/project/list/test_list.test_list_command_from_empty_folder.approved.txt new file mode 100644 index 00000000..9e2695dc --- /dev/null +++ b/tests/project/list/test_list.test_list_command_from_empty_folder.approved.txt @@ -0,0 +1,7 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from /.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from /.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +WARNING: No AlgoKit workspace found. Check [project.type] definition at .algokit.toml diff --git a/tests/project/list/test_list.test_list_command_from_workspace_success.approved.txt b/tests/project/list/test_list.test_list_command_from_workspace_success.approved.txt new file mode 100644 index 00000000..eafeb7cb --- /dev/null +++ b/tests/project/list/test_list.test_list_command_from_workspace_success.approved.txt @@ -0,0 +1,21 @@ +workspace: {current_working_directory} 📁 + - contract_project_1 ({current_working_directory}/projects/project1) 📜 + - contract_project_2 ({current_working_directory}/projects/project2) 📜 + - contract_project_3 ({current_working_directory}/projects/project3) 📜 + - contract_project_4 ({current_working_directory}/projects/project4) 📜 + - contract_project_5 ({current_working_directory}/projects/project5) 📜 + - contract_project_6 ({current_working_directory}/projects/project6) 📜 + - contract_project_7 ({current_working_directory}/projects/project7) 📜 + - contract_project_8 ({current_working_directory}/projects/project8) 📜 + - contract_project_9 ({current_working_directory}/projects/project9) 📜 + - contract_project_10 ({current_working_directory}/projects/project10) 📜 + - contract_project_11 ({current_working_directory}/projects/project11) 📜 + - contract_project_12 ({current_working_directory}/projects/project12) 📜 + - contract_project_13 ({current_working_directory}/projects/project13) 📜 + - contract_project_14 ({current_working_directory}/projects/project14) 📜 + - contract_project_15 ({current_working_directory}/projects/project15) 📜 + - contract_project_16 ({current_working_directory}/projects/project16) 📜 + - contract_project_17 ({current_working_directory}/projects/project17) 📜 + - contract_project_18 ({current_working_directory}/projects/project18) 📜 + - contract_project_19 ({current_working_directory}/projects/project19) 📜 + - contract_project_20 ({current_working_directory}/projects/project20) 📜 diff --git a/tests/project/list/test_list.test_list_command_no_args.approved.txt b/tests/project/list/test_list.test_list_command_no_args.approved.txt new file mode 100644 index 00000000..9e2695dc --- /dev/null +++ b/tests/project/list/test_list.test_list_command_no_args.approved.txt @@ -0,0 +1,7 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from /.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +DEBUG: Attempting to load project config from /.algokit.toml +DEBUG: No .algokit.toml file found in the project directory. +WARNING: No AlgoKit workspace found. Check [project.type] definition at .algokit.toml diff --git a/tests/project/list/test_list.test_list_command_verbose_from_workspace_success.approved.txt b/tests/project/list/test_list.test_list_command_verbose_from_workspace_success.approved.txt new file mode 100644 index 00000000..fd031321 --- /dev/null +++ b/tests/project/list/test_list.test_list_command_verbose_from_workspace_success.approved.txt @@ -0,0 +1,20 @@ +contract_project_1: ({current_working_directory}/projects/project1) 📜 +contract_project_2: ({current_working_directory}/projects/project2) 📜 +contract_project_3: ({current_working_directory}/projects/project3) 📜 +contract_project_4: ({current_working_directory}/projects/project4) 📜 +contract_project_5: ({current_working_directory}/projects/project5) 📜 +contract_project_6: ({current_working_directory}/projects/project6) 📜 +contract_project_7: ({current_working_directory}/projects/project7) 📜 +contract_project_8: ({current_working_directory}/projects/project8) 📜 +contract_project_9: ({current_working_directory}/projects/project9) 📜 +contract_project_10: ({current_working_directory}/projects/project10) 📜 +contract_project_11: ({current_working_directory}/projects/project11) 📜 +contract_project_12: ({current_working_directory}/projects/project12) 📜 +contract_project_13: ({current_working_directory}/projects/project13) 📜 +contract_project_14: ({current_working_directory}/projects/project14) 📜 +contract_project_15: ({current_working_directory}/projects/project15) 📜 +contract_project_16: ({current_working_directory}/projects/project16) 📜 +contract_project_17: ({current_working_directory}/projects/project17) 📜 +contract_project_18: ({current_working_directory}/projects/project18) 📜 +contract_project_19: ({current_working_directory}/projects/project19) 📜 +contract_project_20: ({current_working_directory}/projects/project20) 📜 diff --git a/tests/project/list/test_list.test_run_command_from_workspace_success.approved.txt b/tests/project/list/test_list.test_run_command_from_workspace_success.approved.txt new file mode 100644 index 00000000..7ac7f472 --- /dev/null +++ b/tests/project/list/test_list.test_run_command_from_workspace_success.approved.txt @@ -0,0 +1,21 @@ +DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml +ℹī¸ project1 +ℹī¸ project2 +ℹī¸ project3 +ℹī¸ project4 +ℹī¸ project5 +ℹī¸ project6 +ℹī¸ project7 +ℹī¸ project8 +ℹī¸ project9 +ℹī¸ project10 +ℹī¸ project11 +ℹī¸ project12 +ℹī¸ project13 +ℹī¸ project14 +ℹī¸ project15 +ℹī¸ project16 +ℹī¸ project17 +ℹī¸ project18 +ℹī¸ project19 +ℹī¸ project20 diff --git a/tests/project/run/__init__.py b/tests/project/run/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/project/run/test_run.py b/tests/project/run/test_run.py new file mode 100644 index 00000000..5c2d4640 --- /dev/null +++ b/tests/project/run/test_run.py @@ -0,0 +1,479 @@ +import sys +from collections.abc import Callable +from pathlib import Path + +import pytest +from _pytest.tmpdir import TempPathFactory +from pytest_mock import MockerFixture + +from tests.utils.approvals import verify +from tests.utils.click_invoker import invoke +from tests.utils.proc_mock import ProcMock +from tests.utils.which_mock import WhichMock + +DirWithAppSpecFactory = Callable[[Path], Path] + +PYTHON_EXECUTABLE = sys.executable +# Escaping the python executable path for use in config files on Windows platforms +PYTHON_EXECUTABLE_ESCAPED = PYTHON_EXECUTABLE.replace("\\", "\\\\") + + +def _format_output(output: str) -> str: + """ + Strips lines from the output that start with the specified string. + + Args: + output (str): The output string to process. + start_str (str): The string that identifies the start of lines to be stripped. + + Returns: + str: The processed output with specified lines stripped. + """ + output = "\n".join([line for line in output.split("\n") if not line.startswith("DEBUG")]) + return output.replace(PYTHON_EXECUTABLE_ESCAPED, "").replace("\\", r"\\") + + +@pytest.fixture(autouse=True) +def _disable_animation(mocker: MockerFixture) -> None: + mocker.patch("algokit.core.utils.animate", return_value=None) + + +@pytest.fixture() +def which_mock(mocker: MockerFixture) -> WhichMock: + which_mock = WhichMock() + mocker.patch("algokit.core.utils.shutil.which").side_effect = which_mock.which + return which_mock + + +def _create_project_config( + project_dir: Path, project_type: str, project_name: str, command: str, description: str +) -> None: + """ + Creates a .algokit.toml configuration file in the specified project directory. + + Args: + project_dir (Path): The directory of the project. + project_type (str): The type of the project. + project_name (str): The name of the project. + command (str): The command associated with the project. + description (str): A description of the project. + """ + project_config = f""" +[project] +type = '{project_type}' +name = '{project_name}' + +[project.run] +hello = {{ commands = ['{command}'], description = '{description}' }} + """.strip() + (project_dir / ".algokit.toml").write_text(project_config, encoding="utf-8") + + +def _create_workspace_project( + *, + workspace_dir: Path, + projects: list[dict[str, str]], + mock_command: bool = False, + which_mock: WhichMock | None = None, + proc_mock: ProcMock | None = None, + custom_project_order: list[str] | None = None, +) -> None: + """ + Creates a workspace project and its subprojects within the specified directory. + + Args: + workspace_dir (Path): The directory of the workspace. + projects (list[dict[str, str]]): A list of dictionaries, each representing a project with + keys for directory, type, name, command, and description. + mock_command (bool, optional): Indicates whether to mock the command. Defaults to False. + which_mock (WhichMock | None, optional): The mock object for the 'which' command. Defaults to None. + proc_mock (ProcMock | None, optional): The mock object for the process execution. Defaults to None. + custom_project_order (list[str] | None, optional): Specifies a custom order for project execution. + Defaults to None. + """ + workspace_dir.mkdir() + custom_project_order = custom_project_order if custom_project_order else ["contract_project", "frontend_project"] + (workspace_dir / ".algokit.toml").write_text( + f""" +[project] +type = 'workspace' +projects_root_path = 'projects' + +[project.run] +hello = {custom_project_order} + """.strip(), + encoding="utf-8", + ) + (workspace_dir / "projects").mkdir() + for project in projects: + project_dir = workspace_dir / "projects" / project["dir"] + project_dir.mkdir() + if mock_command and proc_mock and which_mock: + resolved_mocked_cmd = which_mock.add(project["command"]) + proc_mock.set_output([resolved_mocked_cmd], ["picked " + project["command"]]) + + _create_project_config( + project_dir, project["type"], project["name"], project["command"], project["description"] + ) + + +@pytest.fixture() +def cwd_with_workspace_sequential( + tmp_path_factory: TempPathFactory, which_mock: WhichMock, proc_mock: ProcMock +) -> Path: + cwd = tmp_path_factory.mktemp("cwd") / "algokit_project" + projects = [ + { + "dir": "project1", + "type": "contract", + "name": "contract_project", + "command": "command_a", + "description": "Prints hello", + }, + { + "dir": "project2", + "type": "frontend", + "name": "frontend_project", + "command": "command_b", + "description": "Prints hello", + }, + ] + _create_workspace_project( + workspace_dir=cwd, projects=projects, mock_command=True, which_mock=which_mock, proc_mock=proc_mock + ) + + return cwd + + +@pytest.fixture() +def cwd_with_workspace(tmp_path_factory: TempPathFactory, which_mock: WhichMock, proc_mock: ProcMock) -> Path: + """ + Creates a standalone project with a single command. + Single project is specified due to the fact that these are run concurrently, + hence output stability is not guaranteed + """ + + cwd = tmp_path_factory.mktemp("cwd") / "algokit_project" + projects = [ + { + "dir": "project1", + "type": "contract", + "name": "contract_project", + "command": "command_a", + "description": "Prints hello", + }, + ] + _create_workspace_project( + workspace_dir=cwd, projects=projects, mock_command=True, which_mock=which_mock, proc_mock=proc_mock + ) + + return cwd + + +@pytest.fixture() +def cwd_with_standalone(tmp_path_factory: TempPathFactory, which_mock: WhichMock, proc_mock: ProcMock) -> Path: + cwd = tmp_path_factory.mktemp("cwd") / "algokit_project" + cwd.mkdir() + + which_mock.add("command_a") + proc_mock.set_output(["command_a"], ["picked command_a"]) + _create_project_config(cwd, "contract", "contract_project", "command_a", "Prints hello contracts") + + return cwd + + +def test_run_command_from_workspace_success( + cwd_with_workspace: Path, +) -> None: + """ + Verifies successful command execution within a workspace project. + + Args: + cwd_with_workspace (Path): The path to the workspace directory. + """ + result = invoke("project run hello", cwd=cwd_with_workspace) + + assert result.exit_code == 0 + verify(_format_output(result.output)) + + +def test_run_command_from_workspace_sequential_success(cwd_with_workspace_sequential: Path) -> None: + """ + Verifies successful sequential command execution within a workspace project. + + Args: + cwd_with_workspace_sequential (Path): The path to the workspace directory. + """ + result = invoke("project run hello", cwd=cwd_with_workspace_sequential) + + assert result.exit_code == 0 + verify(_format_output(result.output)) + + +def test_run_command_from_standalone(cwd_with_standalone: Path) -> None: + """ + Verifies successful command execution within a standalone project. + + Args: + cwd_with_standalone (Path): The path to the standalone project directory. + """ + result = invoke("project run hello", cwd=cwd_with_standalone) + + assert result.exit_code == 0 + verify(_format_output(result.output)) + + +def test_run_command_from_workspace_filtered(cwd_with_workspace_sequential: Path) -> None: + """ + Verifies successful command execution within a workspace project with filtering by project name. + + Args: + cwd_with_workspace_sequential (Path): The path to the workspace directory. + """ + result = invoke("project run hello --project-name 'contract_project'", cwd=cwd_with_workspace_sequential) + + assert result.exit_code == 0 + verify(_format_output(result.output)) + + +def test_list_all_commands_in_workspace(cwd_with_workspace_sequential: Path) -> None: + """ + Lists all commands available within a workspace project. + + Args: + cwd_with_workspace_sequential (Path): The path to the workspace directory. + """ + result = invoke("project run hello --list", cwd=cwd_with_workspace_sequential) + + assert result.exit_code == 0 + verify(_format_output(result.output)) + + +def test_run_command_from_workspace_filtered_no_project(cwd_with_workspace_sequential: Path) -> None: + """ + Verifies command execution within a workspace project when the specified project does not exist. + + Args: + cwd_with_workspace_sequential (Path): The path to the workspace directory. + """ + result = invoke("project run hello --project-name contract_project2", cwd=cwd_with_workspace_sequential) + + assert result.exit_code == 0 + verify(_format_output(result.output)) + + +def test_run_command_from_workspace_resolution_error( + tmp_path_factory: pytest.TempPathFactory, +) -> None: + """ + Verifies the behavior when a command resolution error occurs within a workspace project. + + Args: + tmp_path_factory (pytest.TempPathFactory): Pytest fixture to create temporary directories. + """ + + cwd = tmp_path_factory.mktemp("cwd") / "algokit_project" + projects = [ + { + "dir": "project2", + "type": "frontend", + "name": "frontend_project", + "command": "failthiscommand", + "description": "Prints hello", + }, + ] + _create_workspace_project( + workspace_dir=cwd, + projects=projects, + ) + + result = invoke("project run hello", cwd=cwd) + + assert result.exit_code == 1 + verify(_format_output(result.output)) + + +def test_run_command_from_workspace_execution_error( + tmp_path_factory: pytest.TempPathFactory, +) -> None: + """ + Verifies the behavior when a command execution error occurs within a workspace project. + + Args: + tmp_path_factory (pytest.TempPathFactory): Pytest fixture to create temporary directories. + """ + cwd = tmp_path_factory.mktemp("cwd") / "algokit_project" + projects = [ + { + "dir": "project2", + "type": "frontend", + "name": "frontend_project", + "command": PYTHON_EXECUTABLE_ESCAPED + ' -c "raise Exception()"', + "description": "Prints hello", + }, + ] + _create_workspace_project( + workspace_dir=cwd, + projects=projects, + ) + + result = invoke("project run hello", cwd=cwd) + + assert result.exit_code == 1 + verify(_format_output(result.output)) + + +def test_run_command_from_standalone_resolution_error( + tmp_path_factory: pytest.TempPathFactory, +) -> None: + """ + Verifies the behavior when a command resolution error occurs within a standalone project. + + Args: + tmp_path_factory (pytest.TempPathFactory): Pytest fixture to create temporary directories. + """ + cwd = tmp_path_factory.mktemp("cwd") / "algokit_project" + projects = [ + { + "dir": "project2", + "type": "frontend", + "name": "frontend_project", + "command": "failthiscommand", + "description": "Prints hello", + }, + ] + _create_workspace_project( + workspace_dir=cwd, + projects=projects, + ) + + result = invoke("project run hello", cwd=cwd / "projects" / "project2") + + assert result.exit_code == 1 + verify(_format_output(result.output)) + + +def test_run_command_from_standalone_execution_error(tmp_path_factory: pytest.TempPathFactory) -> None: + """ + Verifies the behavior when a command execution error occurs within a standalone project. + + Args: + tmp_path_factory (pytest.TempPathFactory): Pytest fixture to create temporary directories. + """ + cwd = tmp_path_factory.mktemp("cwd") / "algokit_project" + cwd.mkdir() + _create_project_config( + cwd, + "contract", + "contract_project", + PYTHON_EXECUTABLE_ESCAPED + ' -c "raise Exception()"', + "Prints hello contracts", + ) + + result = invoke("project run hello", cwd=cwd) + + assert result.exit_code == 1 + verify(_format_output(result.output)) + + +def test_run_command_from_workspace_partially_sequential( + tmp_path_factory: TempPathFactory, which_mock: WhichMock, proc_mock: ProcMock +) -> None: + """ + Verifies successful execution of commands in a partially sequential order within a workspace project. + + Args: + tmp_path_factory (TempPathFactory): Pytest fixture to create temporary directories. + which_mock (WhichMock): Mock object for the 'which' command. + proc_mock (ProcMock): Mock object for process execution. + """ + cwd = tmp_path_factory.mktemp("cwd") / "algokit_project" + projects = [] + for i in range(1, 6): + projects.append( + { + "dir": f"project{i}", + "type": "contract", + "name": f"contract_project_{i}", + "command": f"hello{i}", + "description": "Prints hello", + } + ) + _create_workspace_project( + workspace_dir=cwd, + projects=projects, + mock_command=True, + which_mock=which_mock, + proc_mock=proc_mock, + custom_project_order=["contract_project_1", "contract_project_4"], + ) + + result = invoke("project run hello", cwd=cwd) + assert result.exit_code == 0 + order_of_execution = [line for line in result.output.split("\n") if line.startswith("✅")] + assert "contract_project_1" in order_of_execution[0] + assert "contract_project_4" in order_of_execution[1] + + +def test_run_command_from_standalone_pass_env( + tmp_path_factory: TempPathFactory, +) -> None: + """ + Verifies successful command execution within a standalone project with environment variables passed. + + Args: + tmp_path_factory (TempPathFactory): Pytest fixture to create temporary directories. + """ + cwd = tmp_path_factory.mktemp("cwd") / "algokit_project" + cwd.mkdir() + (cwd / "print_env.py").write_text('import os; print(os.environ.get("HELLO"))') + + _create_project_config( + cwd, + "contract", + "contract_project", + PYTHON_EXECUTABLE_ESCAPED + " print_env.py", + "Prints hello contracts", + ) + result = invoke("project run hello", cwd=cwd, env={"HELLO": "Hello World from env variable!"}) + + assert result.exit_code == 0 + verify(_format_output(result.output)) + + +def test_run_command_help_works_without_path_resolution( + tmp_path_factory: TempPathFactory, + which_mock: WhichMock, + proc_mock: ProcMock, +) -> None: + """ + Verifies that the help command works without path resolution. + """ + + cwd = tmp_path_factory.mktemp("cwd") / "algokit_project" + projects = [] + for i in range(1, 6): + projects.append( + { + "dir": f"project{i}", + "type": "contract", + "name": f"contract_project_{i}", + "command": f"hello{i}", + "description": "Prints hello", + } + ) + _create_workspace_project( + workspace_dir=cwd, + projects=projects, + mock_command=False, + which_mock=which_mock, + proc_mock=proc_mock, + custom_project_order=["contract_project_1", "contract_project_4"], + ) + + result = invoke("project run --help", cwd=cwd) + + assert result.exit_code == 0 + verify(_format_output(result.output)) + + assert invoke("project run hello", cwd=cwd).exit_code == 1 diff --git a/tests/project/run/test_run.test_list_all_commands_in_workspace.approved.txt b/tests/project/run/test_run.test_list_all_commands_in_workspace.approved.txt new file mode 100644 index 00000000..a0362676 --- /dev/null +++ b/tests/project/run/test_run.test_list_all_commands_in_workspace.approved.txt @@ -0,0 +1,2 @@ +ℹī¸ Project: contract_project, Command name: hello, Command(s): command_a +ℹī¸ Project: frontend_project, Command name: hello, Command(s): command_b diff --git a/tests/project/run/test_run.test_run_command_from_standalone.approved.txt b/tests/project/run/test_run.test_run_command_from_standalone.approved.txt new file mode 100644 index 00000000..61c6924c --- /dev/null +++ b/tests/project/run/test_run.test_run_command_from_standalone.approved.txt @@ -0,0 +1,5 @@ +Running `hello` command in {current_working_directory}... +Command Executed: 'command_a' +Output: STDOUT +STDERR +✅ contract_project: 'command_a' executed successfully. diff --git a/tests/project/run/test_run.test_run_command_from_standalone_execution_error.approved.txt b/tests/project/run/test_run.test_run_command_from_standalone_execution_error.approved.txt new file mode 100644 index 00000000..af2946f5 --- /dev/null +++ b/tests/project/run/test_run.test_run_command_from_standalone_execution_error.approved.txt @@ -0,0 +1,8 @@ +Running `hello` command in {current_working_directory}... +ERROR: +¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡ project run 'hello' command output: ¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡ +Traceback (most recent call last): + File "", line 1, in +Exception + +Error: 'hello' failed executing ' -c raise Exception()' with exit code = 1 diff --git a/tests/project/run/test_run.test_run_command_from_standalone_pass_env.approved.txt b/tests/project/run/test_run.test_run_command_from_standalone_pass_env.approved.txt new file mode 100644 index 00000000..b98fdcec --- /dev/null +++ b/tests/project/run/test_run.test_run_command_from_standalone_pass_env.approved.txt @@ -0,0 +1,5 @@ +Running `hello` command in {current_working_directory}... +Command Executed: ' print_env.py' +Output: Hello World from env variable! + +✅ contract_project: ' print_env.py' executed successfully. diff --git a/tests/project/run/test_run.test_run_command_from_standalone_resolution_error.approved.txt b/tests/project/run/test_run.test_run_command_from_standalone_resolution_error.approved.txt new file mode 100644 index 00000000..3d90173f --- /dev/null +++ b/tests/project/run/test_run.test_run_command_from_standalone_resolution_error.approved.txt @@ -0,0 +1,3 @@ +Running `hello` command in {current_working_directory}... +ERROR: 'hello' failed executing: 'failthiscommand' +Error: Failed to resolve command path, 'failthiscommand' wasn't found diff --git a/tests/project/run/test_run.test_run_command_from_workspace_execution_error.approved.txt b/tests/project/run/test_run.test_run_command_from_workspace_execution_error.approved.txt new file mode 100644 index 00000000..80f7b144 --- /dev/null +++ b/tests/project/run/test_run.test_run_command_from_workspace_execution_error.approved.txt @@ -0,0 +1,10 @@ +Detected execution order, running commands sequentially +âŗ frontend_project: 'hello' command in progress... +ERROR: +¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡ project run 'hello' command output: ¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡ +Traceback (most recent call last): + File "", line 1, in +Exception + +ERROR: ❌ frontend_project: 'hello' failed executing ' -c raise Exception()' with exit code = 1 +Error: failed to execute 'hello' command in 'frontend_project' diff --git a/tests/project/run/test_run.test_run_command_from_workspace_filtered.approved.txt b/tests/project/run/test_run.test_run_command_from_workspace_filtered.approved.txt new file mode 100644 index 00000000..1ab29407 --- /dev/null +++ b/tests/project/run/test_run.test_run_command_from_workspace_filtered.approved.txt @@ -0,0 +1,3 @@ +Detected execution order, running commands sequentially +âŗ contract_project: 'hello' command in progress... +✅ contract_project: 'command_a' executed successfully. diff --git a/tests/project/run/test_run.test_run_command_from_workspace_filtered_no_project.approved.txt b/tests/project/run/test_run.test_run_command_from_workspace_filtered_no_project.approved.txt new file mode 100644 index 00000000..43b76eb3 --- /dev/null +++ b/tests/project/run/test_run.test_run_command_from_workspace_filtered_no_project.approved.txt @@ -0,0 +1,2 @@ +Detected execution order, running commands sequentially +WARNING: Missing projects: contract_project2. Proceeding with available ones. diff --git a/tests/project/run/test_run.test_run_command_from_workspace_resolution_error.approved.txt b/tests/project/run/test_run.test_run_command_from_workspace_resolution_error.approved.txt new file mode 100644 index 00000000..58e0dd5a --- /dev/null +++ b/tests/project/run/test_run.test_run_command_from_workspace_resolution_error.approved.txt @@ -0,0 +1,5 @@ +Detected execution order, running commands sequentially +âŗ frontend_project: 'hello' command in progress... +ERROR: 'hello' failed executing: 'failthiscommand' +ERROR: ❌ frontend_project: Failed to resolve command path, 'failthiscommand' wasn't found +Error: failed to execute 'hello' command in 'frontend_project' diff --git a/tests/project/run/test_run.test_run_command_from_workspace_sequential_success.approved.txt b/tests/project/run/test_run.test_run_command_from_workspace_sequential_success.approved.txt new file mode 100644 index 00000000..af8edab5 --- /dev/null +++ b/tests/project/run/test_run.test_run_command_from_workspace_sequential_success.approved.txt @@ -0,0 +1,5 @@ +Detected execution order, running commands sequentially +âŗ contract_project: 'hello' command in progress... +✅ contract_project: 'command_a' executed successfully. +âŗ frontend_project: 'hello' command in progress... +✅ frontend_project: 'command_b' executed successfully. diff --git a/tests/project/run/test_run.test_run_command_from_workspace_success.approved.txt b/tests/project/run/test_run.test_run_command_from_workspace_success.approved.txt new file mode 100644 index 00000000..1ab29407 --- /dev/null +++ b/tests/project/run/test_run.test_run_command_from_workspace_success.approved.txt @@ -0,0 +1,3 @@ +Detected execution order, running commands sequentially +âŗ contract_project: 'hello' command in progress... +✅ contract_project: 'command_a' executed successfully. diff --git a/tests/project/run/test_run.test_run_command_help_works_without_path_resolution.approved.txt b/tests/project/run/test_run.test_run_command_help_works_without_path_resolution.approved.txt new file mode 100644 index 00000000..9884bcac --- /dev/null +++ b/tests/project/run/test_run.test_run_command_help_works_without_path_resolution.approved.txt @@ -0,0 +1,9 @@ +Usage: algokit project run [OPTIONS] COMMAND [ARGS]... + + Define custom commands and manage their execution in you projects. + +Options: + -h, --help Show this message and exit. + +Commands: + hello Run all "hello" commands in the workspace project. diff --git a/tests/test_root.test_help.approved.txt b/tests/test_root.test_help.approved.txt index 7968b1a9..778cf9e5 100644 --- a/tests/test_root.test_help.approved.txt +++ b/tests/test_root.test_help.approved.txt @@ -14,11 +14,11 @@ Options: -h, --help Show this message and exit. Commands: - bootstrap Bootstrap local dependencies in an AlgoKit project; run from - project root directory. + compile Compile smart contracts and smart signatures written in a + supported high-level language to a format deployable on the + Algorand Virtual Machine (AVM). completions Install and Uninstall AlgoKit shell integrations. config Configure AlgoKit settings. - deploy Deploy smart contracts from AlgoKit compliant repository. dispenser Interact with the AlgoKit TestNet Dispenser. doctor Diagnose potential environment issues that may affect AlgoKit. explore Explore the specified network in the browser using Dappflow. @@ -27,4 +27,5 @@ Commands: init Initializes a new project from a template; run from project parent directory. localnet Manage the AlgoKit LocalNet. + project Provides a suite of commands for managing your AlgoKit project. task Collection of useful tasks to help you develop on Algorand. diff --git a/tests/version_check/test_version_check.py b/tests/version_check/test_version_check.py index d33c0bcf..d3a34fd5 100644 --- a/tests/version_check/test_version_check.py +++ b/tests/version_check/test_version_check.py @@ -31,14 +31,14 @@ def _setup(mocker: MockerFixture, app_dir_mock: AppDirs) -> None: mocker.patch("algokit.core.version_prompt.get_app_config_dir").return_value = app_dir_mock.app_config_dir mocker.patch("algokit.core.version_prompt.get_app_state_dir").return_value = app_dir_mock.app_state_dir # make bootstrap env a no-op - mocker.patch("algokit.cli.bootstrap.bootstrap_env") + mocker.patch("algokit.cli.project.bootstrap.bootstrap_env") def test_version_check_queries_github_when_no_cache(app_dir_mock: AppDirs, httpx_mock: HTTPXMock) -> None: httpx_mock.add_response(url=LATEST_URL, json={"tag_name": f"v{NEW_VERSION}"}) # bootstrap env is a nice simple command we can use to test the version check side effects - result = invoke("bootstrap env", skip_version_check=False) + result = invoke("project bootstrap env", skip_version_check=False) assert result.exit_code == 0 verify(result.output, scrubber=make_scrubber(app_dir_mock)) @@ -78,7 +78,7 @@ def test_version_check_only_warns_if_newer_version_is_found( mocker.patch("algokit.core.version_prompt.get_current_package_version").return_value = current_version version_cache = app_dir_mock.app_state_dir / "last-version-check" version_cache.write_text(latest_version, encoding="utf-8") - result = invoke("bootstrap env", skip_version_check=False) + result = invoke("project bootstrap env", skip_version_check=False) if warning_expected: assert f"version {latest_version} is available" in result.output @@ -89,7 +89,7 @@ def test_version_check_only_warns_if_newer_version_is_found( def test_version_check_uses_cache(app_dir_mock: AppDirs) -> None: version_cache = app_dir_mock.app_state_dir / "last-version-check" version_cache.write_text("1234.56.78", encoding="utf-8") - result = invoke("bootstrap env", skip_version_check=False) + result = invoke("project bootstrap env", skip_version_check=False) assert result.exit_code == 0 verify(result.output, scrubber=make_scrubber(app_dir_mock)) @@ -102,7 +102,7 @@ def test_version_check_queries_github_when_cache_out_of_date(app_dir_mock: AppDi modified_time = time() - VERSION_CHECK_INTERVAL - 1 os.utime(version_cache, (modified_time, modified_time)) - result = invoke("bootstrap env", skip_version_check=False) + result = invoke("project bootstrap env", skip_version_check=False) assert result.exit_code == 0 verify(result.output, scrubber=make_scrubber(app_dir_mock)) @@ -110,14 +110,14 @@ def test_version_check_queries_github_when_cache_out_of_date(app_dir_mock: AppDi def test_version_check_respects_disable_config(app_dir_mock: AppDirs) -> None: (app_dir_mock.app_config_dir / "disable-version-prompt").touch() - result = invoke("bootstrap env", skip_version_check=False) + result = invoke("project bootstrap env", skip_version_check=False) assert result.exit_code == 0 verify(result.output, scrubber=make_scrubber(app_dir_mock)) def test_version_check_respects_skip_option(app_dir_mock: AppDirs) -> None: - result = invoke("--skip-version-check bootstrap env", skip_version_check=False) + result = invoke("--skip-version-check project bootstrap env", skip_version_check=False) assert result.exit_code == 0 verify(result.output, scrubber=make_scrubber(app_dir_mock))