Skip to content

Commit 01dcef9

Browse files
committed
feat: npm runs ci instead on install when CI flag is present or passed explicitly`
1 parent 6691cc7 commit 01dcef9

11 files changed

+111
-25
lines changed

docs/cli/index.md

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,10 @@
127127
- [-t, --type ](#-t---type-)
128128
- [Options](#options-20)
129129
- [--interactive, --non-interactive, --ci](#--interactive---non-interactive---ci-1)
130-
- [deploy](#deploy)
131130
- [Options](#options-21)
131+
- [--ci, --no-ci](#--ci---no-ci)
132+
- [deploy](#deploy)
133+
- [Options](#options-22)
132134
- [-C, -c, --command ](#-c--c---command-)
133135
- [--interactive, --non-interactive, --ci](#--interactive---non-interactive---ci-2)
134136
- [-P, --path ](#-p---path-)
@@ -139,7 +141,7 @@
139141
- [ENVIRONMENT_NAME](#environment_name)
140142
- [EXTRA_ARGS](#extra_args)
141143
- [link](#link)
142-
- [Options](#options-22)
144+
- [Options](#options-23)
143145
- [-p, --project-name ](#-p---project-name--2)
144146
- [-l, --language ](#-l---language--1)
145147
- [-a, --all](#-a---all)
@@ -151,7 +153,7 @@
151153
- [run](#run)
152154
- [task](#task)
153155
- [analyze](#analyze)
154-
- [Options](#options-23)
156+
- [Options](#options-24)
155157
- [-r, --recursive](#-r---recursive)
156158
- [--force](#--force-2)
157159
- [--diff](#--diff)
@@ -160,11 +162,11 @@
160162
- [Arguments](#arguments-10)
161163
- [INPUT_PATHS](#input_paths)
162164
- [ipfs](#ipfs)
163-
- [Options](#options-24)
165+
- [Options](#options-25)
164166
- [-f, --file ](#-f---file--1)
165167
- [-n, --name ](#-n---name--2)
166168
- [mint](#mint)
167-
- [Options](#options-25)
169+
- [Options](#options-26)
168170
- [--creator ](#--creator-)
169171
- [--name ](#--name-)
170172
- [-u, --unit ](#-u---unit-)
@@ -176,45 +178,45 @@
176178
- [--mutable, --immutable](#--mutable---immutable)
177179
- [-n, --network ](#-n---network-)
178180
- [nfd-lookup](#nfd-lookup)
179-
- [Options](#options-26)
181+
- [Options](#options-27)
180182
- [-o, --output ](#-o---output--3)
181183
- [Arguments](#arguments-11)
182184
- [VALUE](#value)
183185
- [opt-in](#opt-in)
184-
- [Options](#options-27)
186+
- [Options](#options-28)
185187
- [-a, --account ](#-a---account-)
186188
- [-n, --network ](#-n---network--1)
187189
- [Arguments](#arguments-12)
188190
- [ASSET_IDS](#asset_ids)
189191
- [opt-out](#opt-out)
190-
- [Options](#options-28)
192+
- [Options](#options-29)
191193
- [-a, --account ](#-a---account--1)
192194
- [--all](#--all)
193195
- [-n, --network ](#-n---network--2)
194196
- [Arguments](#arguments-13)
195197
- [ASSET_IDS](#asset_ids-1)
196198
- [send](#send)
197-
- [Options](#options-29)
199+
- [Options](#options-30)
198200
- [-f, --file ](#-f---file--2)
199201
- [-t, --transaction ](#-t---transaction-)
200202
- [-n, --network ](#-n---network--3)
201203
- [sign](#sign)
202-
- [Options](#options-30)
204+
- [Options](#options-31)
203205
- [-a, --account ](#-a---account--2)
204206
- [-f, --file ](#-f---file--3)
205207
- [-t, --transaction ](#-t---transaction--1)
206208
- [-o, --output ](#-o---output--4)
207209
- [--force](#--force-3)
208210
- [transfer](#transfer)
209-
- [Options](#options-31)
211+
- [Options](#options-32)
210212
- [-s, --sender ](#-s---sender-)
211213
- [-r, --receiver ](#-r---receiver--1)
212214
- [--asset, --id ](#--asset---id-)
213215
- [-a, --amount ](#-a---amount--1)
214216
- [--whole-units](#--whole-units-2)
215217
- [-n, --network ](#-n---network--4)
216218
- [vanity-address](#vanity-address)
217-
- [Options](#options-32)
219+
- [Options](#options-33)
218220
- [-m, --match ](#-m---match-)
219221
- [-o, --output ](#-o---output--5)
220222
- [-a, --alias ](#-a---alias-)
@@ -223,19 +225,19 @@
223225
- [Arguments](#arguments-14)
224226
- [KEYWORD](#keyword)
225227
- [wallet](#wallet)
226-
- [Options](#options-33)
228+
- [Options](#options-34)
227229
- [-a, --address ](#-a---address-)
228230
- [-m, --mnemonic](#-m---mnemonic)
229231
- [-f, --force](#-f---force-4)
230232
- [Arguments](#arguments-15)
231233
- [ALIAS_NAME](#alias_name)
232234
- [Arguments](#arguments-16)
233235
- [ALIAS](#alias)
234-
- [Options](#options-34)
236+
- [Options](#options-35)
235237
- [-f, --force](#-f---force-5)
236238
- [Arguments](#arguments-17)
237239
- [ALIAS](#alias-1)
238-
- [Options](#options-35)
240+
- [Options](#options-36)
239241
- [-f, --force](#-f---force-6)
240242

241243
# algokit
@@ -929,6 +931,12 @@ Runs npm install in the current working directory to install Node.js dependencie
929931
algokit project bootstrap npm [OPTIONS]
930932
```
931933

934+
### Options
935+
936+
937+
### --ci, --no-ci
938+
Run 'npm ci' instead of 'npm install' in CI mode (clean install).
939+
932940
#### poetry
933941

934942
Installs Python Poetry (if not present) and runs poetry install in the current working directory to install Python dependencies.

docs/features/project/bootstrap.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ SERVER_PORT=4000
7070

7171
### Bootstrap Node.js project
7272

73-
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.
73+
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.
7474

7575
Here is an example outcome of running `algokit project bootstrap npm` command:
7676

src/algokit/cli/project/bootstrap.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,5 +102,11 @@ def poetry() -> None:
102102
@bootstrap_group.command(
103103
"npm", short_help="Runs `npm install` in the current working directory to install Node.js dependencies."
104104
)
105-
def npm() -> None:
106-
bootstrap_npm(Path.cwd())
105+
@click.option(
106+
"--ci/--no-ci",
107+
is_flag=True,
108+
default=lambda: "CI" in os.environ,
109+
help="Run 'npm ci' instead of 'npm install' in CI mode (clean install).",
110+
)
111+
def npm(*, ci: bool) -> None:
112+
bootstrap_npm(Path.cwd(), ci_mode=ci)

src/algokit/core/project/bootstrap.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def bootstrap_any(project_dir: Path, *, ci_mode: bool) -> None:
3131

3232
if package_json_path.exists():
3333
logger.debug("Running `algokit project bootstrap npm`")
34-
bootstrap_npm(project_dir)
34+
bootstrap_npm(project_dir, ci_mode=ci_mode)
3535

3636

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

178178

179-
def bootstrap_npm(project_dir: Path) -> None:
179+
def bootstrap_npm(project_dir: Path, *, ci_mode: bool) -> None:
180+
def get_install_command(*, ci_mode: bool) -> list[str]:
181+
has_package_lock = (project_dir / "package-lock.json").exists()
182+
if ci_mode and not has_package_lock:
183+
raise click.ClickException(
184+
"Cannot run `npm ci` because `package-lock.json` is missing. "
185+
"Please run `npm install` instead and commit it to your source control."
186+
)
187+
return ["ci" if ci_mode else "install"]
188+
180189
package_json_path = project_dir / "package.json"
181190
if not package_json_path.exists():
182191
logger.info(f"{package_json_path} doesn't exist; nothing to do here, skipping bootstrap of npm")
183192
else:
184193
logger.info("Installing npm dependencies")
185-
cmd = ["npm" if not is_windows() else "npm.cmd", "install"]
194+
cmd = ["npm" if not is_windows() else "npm.cmd", *get_install_command(ci_mode=ci_mode)]
186195
try:
187196
proc.run(
188197
cmd,

tests/project/bootstrap/test_bootstrap_npm.py

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def test_bootstrap_npm_without_npm(
1515
(cwd / "package.json").touch()
1616

1717
result = invoke(
18-
"project bootstrap npm",
18+
"project bootstrap npm --no-ci",
1919
cwd=cwd,
2020
)
2121

@@ -27,7 +27,7 @@ def test_bootstrap_npm_without_npm(
2727
def test_bootstrap_npm_without_package_file(tmp_path_factory: TempPathFactory, request: pytest.FixtureRequest) -> None:
2828
cwd = tmp_path_factory.mktemp("cwd")
2929
result = invoke(
30-
"project bootstrap npm",
30+
"project bootstrap npm --no-ci",
3131
cwd=cwd,
3232
)
3333

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

4646
result = invoke(
47-
"project bootstrap npm",
47+
"project bootstrap npm --no-ci",
4848
cwd=cwd,
4949
)
5050

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

6060
result = invoke(
61-
"project bootstrap npm",
61+
"project bootstrap npm --no-ci",
6262
cwd=cwd,
6363
)
6464

6565
assert result.exit_code == 0
6666
verify(result.output, namer=PyTestNamer(request))
67+
68+
69+
@pytest.mark.usefixtures("mock_platform_system", "proc_mock")
70+
def test_bootstrap_npm_ci_mode_with_lock_file(
71+
tmp_path_factory: TempPathFactory, request: pytest.FixtureRequest
72+
) -> None:
73+
cwd = tmp_path_factory.mktemp("cwd")
74+
(cwd / "package.json").touch()
75+
(cwd / "package-lock.json").touch()
76+
77+
result = invoke(
78+
"project bootstrap npm --ci",
79+
cwd=cwd,
80+
)
81+
82+
assert result.exit_code == 0
83+
verify(result.output, namer=PyTestNamer(request))
84+
85+
86+
@pytest.mark.usefixtures("mock_platform_system", "proc_mock")
87+
def test_bootstrap_npm_ci_mode_without_lock_file(
88+
tmp_path_factory: TempPathFactory, request: pytest.FixtureRequest
89+
) -> None:
90+
cwd = tmp_path_factory.mktemp("cwd")
91+
(cwd / "package.json").touch()
92+
93+
result = invoke(
94+
"project bootstrap npm --ci",
95+
cwd=cwd,
96+
)
97+
98+
assert result.exit_code == 1 # Should fail when no package-lock.json exists
99+
verify(result.output, namer=PyTestNamer(request))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml
2+
DEBUG: No .algokit.toml file found in the project directory.
3+
Installing npm dependencies
4+
DEBUG: Running 'npm ci' in '{current_working_directory}'
5+
npm: STDOUT
6+
npm: STDERR
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml
2+
DEBUG: No .algokit.toml file found in the project directory.
3+
Installing npm dependencies
4+
DEBUG: Running 'npm ci' in '{current_working_directory}'
5+
npm: STDOUT
6+
npm: STDERR
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml
2+
DEBUG: No .algokit.toml file found in the project directory.
3+
Installing npm dependencies
4+
DEBUG: Running 'npm.cmd ci' in '{current_working_directory}'
5+
npm.cmd: STDOUT
6+
npm.cmd: STDERR
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml
2+
DEBUG: No .algokit.toml file found in the project directory.
3+
Installing npm dependencies
4+
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 numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml
2+
DEBUG: No .algokit.toml file found in the project directory.
3+
Installing npm dependencies
4+
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 numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml
2+
DEBUG: No .algokit.toml file found in the project directory.
3+
Installing npm dependencies
4+
Error: Cannot run `npm ci` because `package-lock.json` is missing. Please run `npm install` instead and commit it to your source control.

0 commit comments

Comments
 (0)