diff --git a/CHANGELOG.md b/CHANGELOG.md index 5057f287af..23d1736214 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ## DFX +### fix(generate): add missing typescript types and fix issues with bindings array in dfx.json + ### chore: update Candid UI canister with commit 79d55e7f568aec00e16dd0329926cc7ea8e3a28b ### refactor: Factor out code for calling arbitrary bundled binaries diff --git a/e2e/tests-dfx/generate.bash b/e2e/tests-dfx/generate.bash index 33a1ffcc57..dce4b9f4b9 100755 --- a/e2e/tests-dfx/generate.bash +++ b/e2e/tests-dfx/generate.bash @@ -20,7 +20,6 @@ teardown() { dfx build dfx canister install --all - dfx --version dfx generate assert_file_exists "src/declarations/hello_backend/hello_backend.did" @@ -29,3 +28,88 @@ teardown() { assert_file_exists "src/declarations/hello_backend/index.js" assert_file_exists "src/declarations/hello_backend/index.d.ts" } + +@test "dfx generate creates only JS files" { + jq '.canisters.hello_backend.declarations.bindings=["js"]' dfx.json | sponge dfx.json + + dfx_start + dfx canister create --all + dfx build + dfx canister install --all + + dfx generate + + assert_file_not_exists "src/declarations/hello_backend/hello_backend.did" + assert_file_exists "src/declarations/hello_backend/hello_backend.did.js" + assert_file_not_exists "src/declarations/hello_backend/hello_backend.did.d.ts" + assert_file_exists "src/declarations/hello_backend/index.js" + assert_file_not_exists "src/declarations/hello_backend/index.d.ts" +} + +@test "dfx generate creates only TS files" { + jq '.canisters.hello_backend.declarations.bindings=["ts"]' dfx.json | sponge dfx.json + + dfx_start + dfx canister create --all + dfx build + dfx canister install --all + + dfx generate + + assert_file_not_exists "src/declarations/hello_backend/hello_backend.did" + assert_file_not_exists "src/declarations/hello_backend/hello_backend.did.js" + assert_file_exists "src/declarations/hello_backend/hello_backend.did.d.ts" + assert_file_not_exists "src/declarations/hello_backend/index.js" + assert_file_exists "src/declarations/hello_backend/index.d.ts" +} + +@test "dfx generate creates only JS & TS files" { + jq '.canisters.hello_backend.declarations.bindings=["js", "ts"]' dfx.json | sponge dfx.json + + dfx_start + dfx canister create --all + dfx build + dfx canister install --all + + dfx generate + + assert_file_not_exists "src/declarations/hello_backend/hello_backend.did" + assert_file_exists "src/declarations/hello_backend/hello_backend.did.js" + assert_file_exists "src/declarations/hello_backend/hello_backend.did.d.ts" + assert_file_exists "src/declarations/hello_backend/index.js" + assert_file_exists "src/declarations/hello_backend/index.d.ts" +} + +@test "dfx generate creates only DID files" { + jq '.canisters.hello_backend.declarations.bindings=["did"]' dfx.json | sponge dfx.json + + dfx_start + dfx canister create --all + dfx build + dfx canister install --all + + dfx generate + + assert_file_exists "src/declarations/hello_backend/hello_backend.did" + assert_file_not_exists "src/declarations/hello_backend/hello_backend.did.js" + assert_file_not_exists "src/declarations/hello_backend/hello_backend.did.d.ts" + assert_file_not_exists "src/declarations/hello_backend/index.js" + assert_file_not_exists "src/declarations/hello_backend/index.d.ts" +} + +@test "dfx generate does not create any files" { + jq '.canisters.hello_backend.declarations.bindings=[]' dfx.json | sponge dfx.json + + dfx_start + dfx canister create --all + dfx build + dfx canister install --all + + dfx generate + + assert_file_not_exists "src/declarations/hello_backend/hello_backend.did" + assert_file_not_exists "src/declarations/hello_backend/hello_backend.did.js" + assert_file_not_exists "src/declarations/hello_backend/hello_backend.did.d.ts" + assert_file_not_exists "src/declarations/hello_backend/index.js" + assert_file_not_exists "src/declarations/hello_backend/index.d.ts" +} diff --git a/src/dfx/assets/language_bindings/index.d.ts.hbs b/src/dfx/assets/language_bindings/index.d.ts.hbs index 67b9b56b57..fbe6585993 100644 --- a/src/dfx/assets/language_bindings/index.d.ts.hbs +++ b/src/dfx/assets/language_bindings/index.d.ts.hbs @@ -1,8 +1,12 @@ import { ActorSubclass, HttpAgentOptions, ActorConfig } from '@dfinity/agent'; import { Principal } from '@dfinity/principal'; +import { IDL } from '@dfinity/candid'; import { _SERVICE } from './{{canister_name}}.did'; +export declare const idlFactory: IDL.InterfaceFactory; +export declare const canisterId: string; + export declare interface CreateActorOptions { agentOptions?: HttpAgentOptions; actorOptions?: ActorConfig; diff --git a/src/dfx/src/lib/builders/mod.rs b/src/dfx/src/lib/builders/mod.rs index c5de37b29b..a67ddf2ce2 100644 --- a/src/dfx/src/lib/builders/mod.rs +++ b/src/dfx/src/lib/builders/mod.rs @@ -118,16 +118,6 @@ pub trait CanisterBuilder { ) })?; } - std::fs::create_dir_all(&generate_output_dir).with_context(|| { - format!( - "Failed to create dir: {}", - generate_output_dir.to_string_lossy() - ) - })?; - - let generated_idl_path = self.generate_idl(pool, info, config)?; - - let (env, ty) = check_candid_file(generated_idl_path.as_path())?; let bindings = info .get_declarations_config() @@ -145,6 +135,17 @@ pub trait CanisterBuilder { ); } + std::fs::create_dir_all(&generate_output_dir).with_context(|| { + format!( + "Failed to create dir: {}", + generate_output_dir.to_string_lossy() + ) + })?; + + let generated_idl_path = self.generate_idl(pool, info, config)?; + + let (env, ty) = check_candid_file(generated_idl_path.as_path())?; + // Typescript if bindings.contains(&"ts".to_string()) { let output_did_ts_path = generate_output_dir @@ -158,6 +159,8 @@ pub trait CanisterBuilder { ) })?; eprintln!(" {}", &output_did_ts_path.display()); + + compile_handlebars_files("ts", info, generate_output_dir)?; } // Javascript @@ -174,78 +177,8 @@ pub trait CanisterBuilder { ) })?; eprintln!(" {}", &output_did_js_path.display()); - // index.js - let mut language_bindings = crate::util::assets::language_bindings() - .context("Failed to get language bindings archive.")?; - for f in language_bindings - .entries() - .context("Failed to read language bindings archive entries.")? - { - let mut file = f.context("Failed to read language bindings archive entry.")?; - - let pathname: PathBuf = file - .path() - .context("Failed to read language bindings entry path name.")? - .to_path_buf(); - let extension = pathname.extension(); - let is_template = matches! (extension, Some (ext ) if ext == OsStr::new("hbs")); - - if is_template { - let mut file_contents = String::new(); - file.read_to_string(&mut file_contents) - .context("Failed to read language bindings archive file content.")?; - - // create the handlebars registry - let handlebars = Handlebars::new(); - - let mut data: BTreeMap = BTreeMap::new(); - - let canister_name = &info.get_name().to_string(); - - let node_compatibility = info.get_declarations_config().node_compatibility; - - // Insert only if node outputs are specified - let actor_export = if node_compatibility { - // leave empty for nodejs - "".to_string() - } else { - format!( - r#" -/** - * A ready-to-use agent for the {0} canister - * @type {{import("@dfinity/agent").ActorSubclass}} -*/ -export const {0} = createActor(canisterId);"#, - canister_name - ) - .to_string() - }; - - data.insert("canister_name".to_string(), canister_name); - data.insert("actor_export".to_string(), &actor_export); - - let process_string: String = match &info.get_declarations_config().env_override - { - Some(s) => format!(r#""{}""#, s.clone()), - None => { - format!( - "process.env.{}{}", - &canister_name.to_ascii_uppercase(), - "_CANISTER_ID" - ) - } - }; - - data.insert("canister_name_process_env".to_string(), &process_string); - - let new_file_contents = - handlebars.render_template(&file_contents, &data).unwrap(); - let new_path = generate_output_dir.join(pathname.with_extension("")); - std::fs::write(&new_path, new_file_contents) - .with_context(|| format!("Failed to write to {}.", new_path.display()))?; - } - } + compile_handlebars_files("js", info, generate_output_dir)?; } // Motoko @@ -268,6 +201,7 @@ export const {0} = createActor(canisterId);"#, } else { eprintln!(" {}", &generated_idl_path.display()); } + Ok(()) } @@ -281,6 +215,87 @@ export const {0} = createActor(canisterId);"#, } } +fn compile_handlebars_files( + lang: &str, + info: &CanisterInfo, + generate_output_dir: &Path, +) -> DfxResult { + // index.js + let mut language_bindings = crate::util::assets::language_bindings() + .context("Failed to get language bindings archive.")?; + for f in language_bindings + .entries() + .context("Failed to read language bindings archive entries.")? + { + let mut file = f.context("Failed to read language bindings archive entry.")?; + + let pathname: PathBuf = file + .path() + .context("Failed to read language bindings entry path name.")? + .to_path_buf(); + let file_extension = format!("{}.hbs", lang); + let is_template = pathname + .to_str() + .map_or(false, |name| name.ends_with(&file_extension)); + + if is_template { + let mut file_contents = String::new(); + file.read_to_string(&mut file_contents) + .context("Failed to read language bindings archive file content.")?; + + // create the handlebars registry + let handlebars = Handlebars::new(); + + let mut data: BTreeMap = BTreeMap::new(); + + let canister_name = &info.get_name().to_string(); + + let node_compatibility = info.get_declarations_config().node_compatibility; + + // Insert only if node outputs are specified + let actor_export = if node_compatibility { + // leave empty for nodejs + "".to_string() + } else { + format!( + r#" + +/** + * A ready-to-use agent for the {0} canister + * @type {{import("@dfinity/agent").ActorSubclass}} +*/ +export const {0} = createActor(canisterId);"#, + canister_name + ) + .to_string() + }; + + data.insert("canister_name".to_string(), canister_name); + data.insert("actor_export".to_string(), &actor_export); + + let process_string: String = match &info.get_declarations_config().env_override { + Some(s) => format!(r#""{}""#, s.clone()), + None => { + format!( + "process.env.{}{}", + &canister_name.to_ascii_uppercase(), + "_CANISTER_ID" + ) + } + }; + + data.insert("canister_name_process_env".to_string(), &process_string); + + let new_file_contents = handlebars.render_template(&file_contents, &data).unwrap(); + let new_path = generate_output_dir.join(pathname.with_extension("")); + std::fs::write(&new_path, new_file_contents) + .with_context(|| format!("Failed to write to {}.", new_path.display()))?; + } + } + + Ok(()) +} + // TODO: this function was copied from src/lib/models/canister.rs fn ensure_trailing_newline(s: String) -> String { if s.ends_with('\n') {