Skip to content

Commit

Permalink
feat: npm runs ci instead of install when CI flag is present or p…
Browse files Browse the repository at this point in the history
…assed explicitly (#605)
  • Loading branch information
aorumbayev authored Jan 15, 2025
1 parent 6691cc7 commit cc8a95d
Show file tree
Hide file tree
Showing 12 changed files with 118 additions and 32 deletions.
46 changes: 27 additions & 19 deletions docs/cli/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,17 @@
- [Options](#options-18)
- [--force](#--force-1)
- [Options](#options-19)
- [--interactive, --non-interactive, --ci](#--interactive---non-interactive---ci)
- [--interactive, --no-ci, --non-interactive, --ci](#--interactive---no-ci---non-interactive---ci)
- [-p, --project-name ](#-p---project-name-)
- [-t, --type ](#-t---type-)
- [Options](#options-20)
- [--interactive, --non-interactive, --ci](#--interactive---non-interactive---ci-1)
- [deploy](#deploy)
- [--interactive, --non-interactive, --ci](#--interactive---non-interactive---ci)
- [Options](#options-21)
- [--ci, --no-ci](#--ci---no-ci)
- [deploy](#deploy)
- [Options](#options-22)
- [-C, -c, --command ](#-c--c---command-)
- [--interactive, --non-interactive, --ci](#--interactive---non-interactive---ci-2)
- [--interactive, --non-interactive, --ci](#--interactive---non-interactive---ci-1)
- [-P, --path ](#-p---path-)
- [--deployer ](#--deployer-)
- [--dispenser ](#--dispenser-)
Expand All @@ -139,7 +141,7 @@
- [ENVIRONMENT_NAME](#environment_name)
- [EXTRA_ARGS](#extra_args)
- [link](#link)
- [Options](#options-22)
- [Options](#options-23)
- [-p, --project-name ](#-p---project-name--2)
- [-l, --language ](#-l---language--1)
- [-a, --all](#-a---all)
Expand All @@ -151,7 +153,7 @@
- [run](#run)
- [task](#task)
- [analyze](#analyze)
- [Options](#options-23)
- [Options](#options-24)
- [-r, --recursive](#-r---recursive)
- [--force](#--force-2)
- [--diff](#--diff)
Expand All @@ -160,11 +162,11 @@
- [Arguments](#arguments-10)
- [INPUT_PATHS](#input_paths)
- [ipfs](#ipfs)
- [Options](#options-24)
- [Options](#options-25)
- [-f, --file ](#-f---file--1)
- [-n, --name ](#-n---name--2)
- [mint](#mint)
- [Options](#options-25)
- [Options](#options-26)
- [--creator ](#--creator-)
- [--name ](#--name-)
- [-u, --unit ](#-u---unit-)
Expand All @@ -176,45 +178,45 @@
- [--mutable, --immutable](#--mutable---immutable)
- [-n, --network ](#-n---network-)
- [nfd-lookup](#nfd-lookup)
- [Options](#options-26)
- [Options](#options-27)
- [-o, --output ](#-o---output--3)
- [Arguments](#arguments-11)
- [VALUE](#value)
- [opt-in](#opt-in)
- [Options](#options-27)
- [Options](#options-28)
- [-a, --account ](#-a---account-)
- [-n, --network ](#-n---network--1)
- [Arguments](#arguments-12)
- [ASSET_IDS](#asset_ids)
- [opt-out](#opt-out)
- [Options](#options-28)
- [Options](#options-29)
- [-a, --account ](#-a---account--1)
- [--all](#--all)
- [-n, --network ](#-n---network--2)
- [Arguments](#arguments-13)
- [ASSET_IDS](#asset_ids-1)
- [send](#send)
- [Options](#options-29)
- [Options](#options-30)
- [-f, --file ](#-f---file--2)
- [-t, --transaction ](#-t---transaction-)
- [-n, --network ](#-n---network--3)
- [sign](#sign)
- [Options](#options-30)
- [Options](#options-31)
- [-a, --account ](#-a---account--2)
- [-f, --file ](#-f---file--3)
- [-t, --transaction ](#-t---transaction--1)
- [-o, --output ](#-o---output--4)
- [--force](#--force-3)
- [transfer](#transfer)
- [Options](#options-31)
- [Options](#options-32)
- [-s, --sender ](#-s---sender-)
- [-r, --receiver ](#-r---receiver--1)
- [--asset, --id ](#--asset---id-)
- [-a, --amount ](#-a---amount--1)
- [--whole-units](#--whole-units-2)
- [-n, --network ](#-n---network--4)
- [vanity-address](#vanity-address)
- [Options](#options-32)
- [Options](#options-33)
- [-m, --match ](#-m---match-)
- [-o, --output ](#-o---output--5)
- [-a, --alias ](#-a---alias-)
Expand All @@ -223,19 +225,19 @@
- [Arguments](#arguments-14)
- [KEYWORD](#keyword)
- [wallet](#wallet)
- [Options](#options-33)
- [Options](#options-34)
- [-a, --address ](#-a---address-)
- [-m, --mnemonic](#-m---mnemonic)
- [-f, --force](#-f---force-4)
- [Arguments](#arguments-15)
- [ALIAS_NAME](#alias_name)
- [Arguments](#arguments-16)
- [ALIAS](#alias)
- [Options](#options-34)
- [Options](#options-35)
- [-f, --force](#-f---force-5)
- [Arguments](#arguments-17)
- [ALIAS](#alias-1)
- [Options](#options-35)
- [Options](#options-36)
- [-f, --force](#-f---force-6)

# algokit
Expand Down Expand Up @@ -890,7 +892,7 @@ algokit project bootstrap all [OPTIONS]
### Options


### --interactive, --non-interactive, --ci
### --interactive, --no-ci, --non-interactive, --ci
Enable/disable interactive prompts. If the CI environment variable is set, defaults to non-interactive


Expand Down Expand Up @@ -929,6 +931,12 @@ Runs npm install in the current working directory to install Node.js dependencie
algokit project bootstrap npm [OPTIONS]
```

### Options


### --ci, --no-ci
Run 'npm ci' instead of 'npm install' in CI mode (clean install).

#### poetry

Installs Python Poetry (if not present) and runs poetry install in the current working directory to install Python dependencies.
Expand Down
2 changes: 1 addition & 1 deletion docs/features/project/bootstrap.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ SERVER_PORT=4000

### Bootstrap Node.js project

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.
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. However, when running in CI mode **with** present `package-lock.json` file (either by setting the `CI` environment variable or using the `--ci` flag), it will run `npm ci` instead, which provides a cleaner and more deterministic installation. If `package-lock.json` is missing, it will show a clear error message and resolution instructions. 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 project bootstrap npm` command:

Expand Down
12 changes: 9 additions & 3 deletions src/algokit/cli/project/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def bootstrap_group(ctx: click.Context, *, force: bool) -> None:
)
@click.option(
"--interactive/--non-interactive",
" /--ci", # this aliases --non-interactive to --ci
"--no-ci/--ci", # this aliases --non-interactive to --ci and --interactive to --no-ci
default=lambda: "CI" not in os.environ,
help="Enable/disable interactive prompts. If the CI environment variable is set, defaults to non-interactive",
)
Expand Down Expand Up @@ -102,5 +102,11 @@ def poetry() -> None:
@bootstrap_group.command(
"npm", short_help="Runs `npm install` in the current working directory to install Node.js dependencies."
)
def npm() -> None:
bootstrap_npm(Path.cwd())
@click.option(
"--ci/--no-ci",
is_flag=True,
default=lambda: "CI" in os.environ,
help="Run 'npm ci' instead of 'npm install' in CI mode (clean install).",
)
def npm(*, ci: bool) -> None:
bootstrap_npm(Path.cwd(), ci_mode=ci)
15 changes: 12 additions & 3 deletions src/algokit/core/project/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def bootstrap_any(project_dir: Path, *, ci_mode: bool) -> None:

if package_json_path.exists():
logger.debug("Running `algokit project bootstrap npm`")
bootstrap_npm(project_dir)
bootstrap_npm(project_dir, ci_mode=ci_mode)


def bootstrap_any_including_subdirs( # noqa: PLR0913
Expand Down Expand Up @@ -176,13 +176,22 @@ def bootstrap_poetry(project_dir: Path) -> None:
raise # unexpected error, we already ran without IOError before


def bootstrap_npm(project_dir: Path) -> None:
def bootstrap_npm(project_dir: Path, *, ci_mode: bool) -> None:
def get_install_command(*, ci_mode: bool) -> list[str]:
has_package_lock = (project_dir / "package-lock.json").exists()
if ci_mode and not has_package_lock:
raise click.ClickException(
"Cannot run `npm ci` because `package-lock.json` is missing. "
"Please run `npm install` instead and commit it to your source control."
)
return ["ci" if ci_mode else "install"]

package_json_path = project_dir / "package.json"
if not package_json_path.exists():
logger.info(f"{package_json_path} doesn't exist; nothing to do here, skipping bootstrap of npm")
else:
logger.info("Installing npm dependencies")
cmd = ["npm" if not is_windows() else "npm.cmd", "install"]
cmd = ["npm" if not is_windows() else "npm.cmd", *get_install_command(ci_mode=ci_mode)]
try:
proc.run(
cmd,
Expand Down
4 changes: 2 additions & 2 deletions tests/project/bootstrap/test_bootstrap_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def test_bootstrap_all_npm(tmp_path_factory: TempPathFactory, request: pytest.Fi
(cwd / "package.json").touch()

result = invoke(
"project bootstrap all",
"project bootstrap all --interactive",
cwd=cwd,
)

Expand Down Expand Up @@ -206,7 +206,7 @@ def test_bootstrap_all_projects_type_filter(tmp_path_factory: TempPathFactory) -
_setup_standalone_project(cwd, "project_3", "contract")
_setup_standalone_project(cwd, "project_4", "frontend")

result = invoke("project bootstrap all --type frontend", cwd=cwd)
result = invoke("project bootstrap all --type frontend --interactive", cwd=cwd)

assert result.exit_code == 0
verify(result.output.replace(".cmd", ""))
Expand Down
41 changes: 37 additions & 4 deletions tests/project/bootstrap/test_bootstrap_npm.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def test_bootstrap_npm_without_npm(
(cwd / "package.json").touch()

result = invoke(
"project bootstrap npm",
"project bootstrap npm --no-ci",
cwd=cwd,
)

Expand All @@ -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(
"project bootstrap npm",
"project bootstrap npm --no-ci",
cwd=cwd,
)

Expand All @@ -44,7 +44,7 @@ def test_bootstrap_npm_without_npm_and_package_file(
cwd = tmp_path_factory.mktemp("cwd")

result = invoke(
"project bootstrap npm",
"project bootstrap npm --no-ci",
cwd=cwd,
)

Expand All @@ -58,9 +58,42 @@ def test_bootstrap_npm_happy_path(tmp_path_factory: TempPathFactory, request: py
(cwd / "package.json").touch()

result = invoke(
"project bootstrap npm",
"project bootstrap npm --no-ci",
cwd=cwd,
)

assert result.exit_code == 0
verify(result.output, namer=PyTestNamer(request))


@pytest.mark.usefixtures("mock_platform_system", "proc_mock")
def test_bootstrap_npm_ci_mode_with_lock_file(
tmp_path_factory: TempPathFactory, request: pytest.FixtureRequest
) -> None:
cwd = tmp_path_factory.mktemp("cwd")
(cwd / "package.json").touch()
(cwd / "package-lock.json").touch()

result = invoke(
"project bootstrap npm --ci",
cwd=cwd,
)

assert result.exit_code == 0
verify(result.output, namer=PyTestNamer(request))


@pytest.mark.usefixtures("mock_platform_system", "proc_mock")
def test_bootstrap_npm_ci_mode_without_lock_file(
tmp_path_factory: TempPathFactory, request: pytest.FixtureRequest
) -> None:
cwd = tmp_path_factory.mktemp("cwd")
(cwd / "package.json").touch()

result = invoke(
"project bootstrap npm --ci",
cwd=cwd,
)

assert result.exit_code == 1 # Should fail when no package-lock.json exists
verify(result.output, namer=PyTestNamer(request))
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml
DEBUG: No .algokit.toml file found in the project directory.
Installing npm dependencies
DEBUG: Running 'npm ci' in '{current_working_directory}'
npm: STDOUT
npm: STDERR
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml
DEBUG: No .algokit.toml file found in the project directory.
Installing npm dependencies
DEBUG: Running 'npm ci' in '{current_working_directory}'
npm: STDOUT
npm: STDERR
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml
DEBUG: No .algokit.toml file found in the project directory.
Installing npm dependencies
DEBUG: Running 'npm.cmd ci' in '{current_working_directory}'
npm.cmd: STDOUT
npm.cmd: STDERR
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml
DEBUG: No .algokit.toml file found in the project directory.
Installing npm dependencies
Error: Cannot run `npm ci` because `package-lock.json` is missing. Please run `npm install` instead and commit it to your source control.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml
DEBUG: No .algokit.toml file found in the project directory.
Installing npm dependencies
Error: Cannot run `npm ci` because `package-lock.json` is missing. Please run `npm install` instead and commit it to your source control.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml
DEBUG: No .algokit.toml file found in the project directory.
Installing npm dependencies
Error: Cannot run `npm ci` because `package-lock.json` is missing. Please run `npm install` instead and commit it to your source control.

0 comments on commit cc8a95d

Please sign in to comment.