Skip to content

Commit

Permalink
fix(generate): add missing typescript types and fix issues with bindi…
Browse files Browse the repository at this point in the history
…ngs array in dfx.json (#2616)

fixes #2615
  • Loading branch information
nathanosdev authored Oct 3, 2022
1 parent 92caaab commit 873fe7c
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 82 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
86 changes: 85 additions & 1 deletion e2e/tests-dfx/generate.bash
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
}
4 changes: 4 additions & 0 deletions src/dfx/assets/language_bindings/index.d.ts.hbs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
177 changes: 96 additions & 81 deletions src/dfx/src/lib/builders/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Expand All @@ -158,6 +159,8 @@ pub trait CanisterBuilder {
)
})?;
eprintln!(" {}", &output_did_ts_path.display());

compile_handlebars_files("ts", info, generate_output_dir)?;
}

// Javascript
Expand All @@ -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<String, &String> = 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<import("./{0}.did.js")._SERVICE>}}
*/
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
Expand All @@ -268,6 +201,7 @@ export const {0} = createActor(canisterId);"#,
} else {
eprintln!(" {}", &generated_idl_path.display());
}

Ok(())
}

Expand All @@ -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<String, &String> = 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<import("./{0}.did.js")._SERVICE>}}
*/
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') {
Expand Down

0 comments on commit 873fe7c

Please sign in to comment.