Skip to content

Commit f95aa9e

Browse files
authored
Merge branch 'main' into vivee/canister-metadata
2 parents 6111576 + 5391e11 commit f95aa9e

File tree

11 files changed

+288
-39
lines changed

11 files changed

+288
-39
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: Validate Examples
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
9+
env:
10+
# When getting Rust dependencies, retry on network error:
11+
CARGO_NET_RETRY: 10
12+
# Use the local .curlrc
13+
CURL_HOME: .
14+
15+
jobs:
16+
validate-examples:
17+
name: Validate Examples on ${{ matrix.os }}
18+
runs-on: ${{ matrix.os }}
19+
20+
strategy:
21+
fail-fast: false
22+
matrix:
23+
os: [ubuntu-latest]
24+
25+
steps:
26+
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
27+
28+
- name: Setup image (Linux)
29+
if: ${{ contains(matrix.os, 'ubuntu') }}
30+
run: ./.github/scripts/provision-linux-build.sh
31+
32+
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
33+
with:
34+
path: |
35+
~/.rustup/
36+
~/.cargo/bin/
37+
~/.cargo/registry/index/
38+
~/.cargo/registry/cache/
39+
~/.cargo/git/db/
40+
target/
41+
key: ${{ matrix.os }}-cargo-${{ hashFiles('rust-toolchain.toml') }}-${{ hashFiles('**/Cargo.lock') }}
42+
43+
- name: Build icp CLI
44+
run: cargo build
45+
46+
- name: Validate examples
47+
run: ./scripts/validate-examples.sh
48+
49+
aggregate:
50+
name: validate-examples:required
51+
if: ${{ always() }}
52+
runs-on: ubuntu-latest
53+
needs: [validate-examples]
54+
steps:
55+
- name: check result
56+
if: ${{ needs.validate-examples.result != 'success' }}
57+
run: exit 1

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Unreleased
22

33
* feat: `icp canister metadata <canister> <metadata section>` now fetches metadata sections from specified canisters
4+
* fix: Validate explicit canister paths and throw an error if `canister.yaml` is not found
45

56
# v0.1.0-beta.3
67

crates/icp-cli/src/operations/settings.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use snafu::{ResultExt, Snafu};
1212
use crate::progress::{ProgressManager, ProgressManagerSettings};
1313

1414
#[derive(Debug, Snafu)]
15+
#[allow(clippy::enum_variant_names)]
1516
pub(crate) enum SyncSettingsOperationError {
1617
#[snafu(display("failed to fetch current canister settings for canister {canister}"))]
1718
FetchCurrentSettings {

crates/icp-cli/tests/project_tests.rs

Lines changed: 127 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -139,34 +139,132 @@ fn glob_path() {
139139
.success();
140140
}
141141

142-
// TODO(or.ricon): This test is currently not passing, fix it.
143-
// #[test]
144-
// fn explicit_path_missing() {
145-
// let ctx = TestContext::new();
146-
147-
// // Setup project
148-
// let project_dir = ctx.create_project_dir("icp");
149-
150-
// // Project manifest
151-
// let pm = r#"
152-
// canisters:
153-
// - my-canister
154-
// "#;
155-
156-
// write_string(
157-
// &project_dir.join("icp.yaml"), // path
158-
// pm, // contents
159-
// )
160-
// .expect("failed to write project manifest");
161-
162-
// // Invoke build
163-
// ctx.icp()
164-
// .current_dir(project_dir)
165-
// .args(["build"])
166-
// .assert()
167-
// .failure()
168-
// .stderr(eq("Error: canister path must exist and be a directory \'my-canister\'").trim());
169-
// }
142+
#[test]
143+
fn explicit_path_missing() {
144+
let ctx = TestContext::new();
145+
146+
// Setup project
147+
let project_dir = ctx.create_project_dir("icp");
148+
149+
// Project manifest
150+
let pm = r#"
151+
canisters:
152+
- my-canister
153+
"#;
154+
155+
write_string(
156+
&project_dir.join("icp.yaml"), // path
157+
pm, // contents
158+
)
159+
.expect("failed to write project manifest");
160+
161+
// Invoke project show
162+
ctx.icp()
163+
.current_dir(project_dir)
164+
.args(["project", "show"])
165+
.assert()
166+
.failure()
167+
.stderr(contains(
168+
"could not locate a canister manifest at: 'my-canister'",
169+
));
170+
}
171+
172+
#[test]
173+
fn explicit_path_missing_canister_yaml() {
174+
let ctx = TestContext::new();
175+
176+
// Setup project
177+
let project_dir = ctx.create_project_dir("icp");
178+
179+
// Project manifest
180+
let pm = r#"
181+
canisters:
182+
- my-canister
183+
"#;
184+
185+
write_string(
186+
&project_dir.join("icp.yaml"), // path
187+
pm, // contents
188+
)
189+
.expect("failed to write project manifest");
190+
191+
// Create directory but no canister.yaml
192+
create_dir_all(&project_dir.join("my-canister")).expect("failed to create canister directory");
193+
194+
// Invoke project show
195+
ctx.icp()
196+
.current_dir(project_dir)
197+
.args(["project", "show"])
198+
.assert()
199+
.failure()
200+
.stderr(contains(
201+
"could not locate a canister manifest at: 'my-canister'",
202+
));
203+
}
204+
205+
#[test]
206+
fn explicit_path_with_subdirectory() {
207+
let ctx = TestContext::new();
208+
209+
// Setup project
210+
let project_dir = ctx.create_project_dir("icp");
211+
212+
// Project manifest
213+
let pm = r#"
214+
canisters:
215+
- canisters/backend
216+
- canisters/frontend
217+
"#;
218+
219+
write_string(
220+
&project_dir.join("icp.yaml"), // path
221+
pm, // contents
222+
)
223+
.expect("failed to write project manifest");
224+
225+
// Backend canister manifest
226+
let backend_cm = indoc! {r#"
227+
name: backend
228+
build:
229+
steps:
230+
- type: script
231+
command: echo "build"
232+
"#};
233+
234+
create_dir_all(&project_dir.join("canisters/backend"))
235+
.expect("failed to create backend directory");
236+
237+
write_string(
238+
&project_dir.join("canisters/backend/canister.yaml"),
239+
backend_cm,
240+
)
241+
.expect("failed to write backend manifest");
242+
243+
// Frontend canister manifest
244+
let frontend_cm = indoc! {r#"
245+
name: frontend
246+
build:
247+
steps:
248+
- type: script
249+
command: echo "build"
250+
"#};
251+
252+
create_dir_all(&project_dir.join("canisters/frontend"))
253+
.expect("failed to create frontend directory");
254+
255+
write_string(
256+
&project_dir.join("canisters/frontend/canister.yaml"),
257+
frontend_cm,
258+
)
259+
.expect("failed to write frontend manifest");
260+
261+
// Invoke project show - should succeed
262+
ctx.icp()
263+
.current_dir(&project_dir)
264+
.args(["project", "show"])
265+
.assert()
266+
.success();
267+
}
170268

171269
#[test]
172270
fn redefine_mainnet_network_disallowed() {
@@ -189,7 +287,7 @@ fn redefine_mainnet_network_disallowed() {
189287
// Any command that loads the project should fail
190288
ctx.icp()
191289
.current_dir(project_dir)
192-
.args(["build"])
290+
.args(["project", "show"])
193291
.assert()
194292
.failure()
195293
.stderr(contains("`mainnet` is a reserved network name"));

crates/icp/src/project.rs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ pub async fn consolidate_manifest(
143143
for i in &m.canisters {
144144
let ms = match i {
145145
Item::Path(pattern) => {
146-
let paths = match is_glob(pattern) {
146+
let is_glob_pattern = is_glob(pattern);
147+
let paths = match is_glob_pattern {
147148
// Explicit path
148149
false => vec![pdir.join(pattern)],
149150

@@ -163,11 +164,28 @@ pub async fn consolidate_manifest(
163164
}
164165
};
165166

166-
let paths = paths
167-
.into_iter()
168-
.filter(|p| p.is_dir()) // Skip missing directories
169-
.filter(|p| p.join(CANISTER_MANIFEST).exists()) // Skip non-canister directories
170-
.collect::<Vec<_>>();
167+
let paths = if is_glob_pattern {
168+
// For glob patterns, filter out non-directories and non-canister directories
169+
paths
170+
.into_iter()
171+
.filter(|p| p.is_dir())
172+
.filter(|p| p.join(CANISTER_MANIFEST).exists())
173+
.collect::<Vec<_>>()
174+
} else {
175+
// For explicit paths, validate that they exist and contain canister.yaml
176+
let mut validated_paths = vec![];
177+
for p in paths {
178+
if !p.join(CANISTER_MANIFEST).is_file() {
179+
return NotFoundSnafu {
180+
kind: "canister".to_string(),
181+
path: pattern.to_string(),
182+
}
183+
.fail();
184+
}
185+
validated_paths.push(p);
186+
}
187+
validated_paths
188+
};
171189

172190
let mut ms = vec![];
173191

examples/icp-recipe-registry-official/icp.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ canisters:
1717

1818
- name: my-canister-with-version
1919
recipe:
20-
type: "@dfinity/prebuilt@v1.0.5"
20+
type: "@dfinity/prebuilt@v2.0.0"
2121
configuration:
2222
path: ../icp-pre-built/dist/hello_world.wasm
2323
sha256: 17a05e36278cd04c7ae6d3d3226c136267b9df7525a0657521405e22ec96be7a

examples/icp-recipe-remote-url-official/icp.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
canisters:
44
- name: my-canister
55
recipe:
6-
type: https://github.com/rikonor/icp-recipes/releases/download/prebuilt-v0.1.2/recipe.hbs
6+
type: https://github.com/dfinity/icp-cli-recipes/releases/download/prebuilt-v2.0.0/recipe.hbs
77
configuration:
88
path: ../icp-pre-built/dist/hello_world.wasm
99
sha256: 17a05e36278cd04c7ae6d3d3226c136267b9df7525a0657521405e22ec96be7a
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
# Start the HTTP server in the background
6+
python3 -m http.server 8080 &
7+
SERVER_PID=$!
8+
9+
# Give the server a moment to start
10+
sleep 1
11+
12+
# Function to cleanup server on exit
13+
cleanup() {
14+
echo "Stopping HTTP server (PID: $SERVER_PID)"
15+
kill $SERVER_PID 2>/dev/null || true
16+
wait $SERVER_PID 2>/dev/null || true
17+
}
18+
19+
# Ensure cleanup happens on script exit
20+
trap cleanup EXIT
21+
22+
# Run icp project show
23+
icp project show

examples/icp-wasm-metadata-recipe/icp.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
canisters:
44
- name: my-canister
55
recipe:
6-
type: prebuilt
6+
type: "@dfinity/prebuilt"
77
configuration:
88
path: ../icp-pre-built/dist/hello_world.wasm
99
sha256: 17a05e36278cd04c7ae6d3d3226c136267b9df7525a0657521405e22ec96be7a

examples/icp-wasm-optimization-recipe/icp.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
canisters:
44
- name: my-canister
55
recipe:
6-
type: prebuilt
6+
type: "@dfinity/prebuilt"
77
configuration:
88
path: ../icp-pre-built/dist/hello_world.wasm
99
sha256: 17a05e36278cd04c7ae6d3d3226c136267b9df7525a0657521405e22ec96be7a

0 commit comments

Comments
 (0)