Skip to content

Commit 15a20e2

Browse files
committed
fix(generate): add missing typescript types and fix issues with bindings array in dfx.json
fixes #2615
1 parent 92caaab commit 15a20e2

4 files changed

Lines changed: 187 additions & 82 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
## DFX
66

7+
### fix(generate): add missing typescript types and fix issues with bindings array in dfx.json
8+
79
### chore: update Candid UI canister with commit 79d55e7f568aec00e16dd0329926cc7ea8e3a28b
810

911
### refactor: Factor out code for calling arbitrary bundled binaries

e2e/tests-dfx/generate.bash

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ teardown() {
2020
dfx build
2121
dfx canister install --all
2222

23-
dfx --version
2423
dfx generate
2524

2625
assert_file_exists "src/declarations/hello_backend/hello_backend.did"
@@ -29,3 +28,88 @@ teardown() {
2928
assert_file_exists "src/declarations/hello_backend/index.js"
3029
assert_file_exists "src/declarations/hello_backend/index.d.ts"
3130
}
31+
32+
@test "dfx generate creates only JS files" {
33+
jq '.canisters.hello_backend.declarations.bindings=["js"]' dfx.json | sponge dfx.json
34+
35+
dfx_start
36+
dfx canister create --all
37+
dfx build
38+
dfx canister install --all
39+
40+
dfx generate
41+
42+
assert_file_not_exists "src/declarations/hello_backend/hello_backend.did"
43+
assert_file_exists "src/declarations/hello_backend/hello_backend.did.js"
44+
assert_file_not_exists "src/declarations/hello_backend/hello_backend.did.d.ts"
45+
assert_file_exists "src/declarations/hello_backend/index.js"
46+
assert_file_not_exists "src/declarations/hello_backend/index.d.ts"
47+
}
48+
49+
@test "dfx generate creates only TS files" {
50+
jq '.canisters.hello_backend.declarations.bindings=["ts"]' dfx.json | sponge dfx.json
51+
52+
dfx_start
53+
dfx canister create --all
54+
dfx build
55+
dfx canister install --all
56+
57+
dfx generate
58+
59+
assert_file_not_exists "src/declarations/hello_backend/hello_backend.did"
60+
assert_file_not_exists "src/declarations/hello_backend/hello_backend.did.js"
61+
assert_file_exists "src/declarations/hello_backend/hello_backend.did.d.ts"
62+
assert_file_not_exists "src/declarations/hello_backend/index.js"
63+
assert_file_exists "src/declarations/hello_backend/index.d.ts"
64+
}
65+
66+
@test "dfx generate creates only JS & TS files" {
67+
jq '.canisters.hello_backend.declarations.bindings=["js", "ts"]' dfx.json | sponge dfx.json
68+
69+
dfx_start
70+
dfx canister create --all
71+
dfx build
72+
dfx canister install --all
73+
74+
dfx generate
75+
76+
assert_file_not_exists "src/declarations/hello_backend/hello_backend.did"
77+
assert_file_exists "src/declarations/hello_backend/hello_backend.did.js"
78+
assert_file_exists "src/declarations/hello_backend/hello_backend.did.d.ts"
79+
assert_file_exists "src/declarations/hello_backend/index.js"
80+
assert_file_exists "src/declarations/hello_backend/index.d.ts"
81+
}
82+
83+
@test "dfx generate creates only DID files" {
84+
jq '.canisters.hello_backend.declarations.bindings=["did"]' dfx.json | sponge dfx.json
85+
86+
dfx_start
87+
dfx canister create --all
88+
dfx build
89+
dfx canister install --all
90+
91+
dfx generate
92+
93+
assert_file_exists "src/declarations/hello_backend/hello_backend.did"
94+
assert_file_not_exists "src/declarations/hello_backend/hello_backend.did.js"
95+
assert_file_not_exists "src/declarations/hello_backend/hello_backend.did.d.ts"
96+
assert_file_not_exists "src/declarations/hello_backend/index.js"
97+
assert_file_not_exists "src/declarations/hello_backend/index.d.ts"
98+
}
99+
100+
@test "dfx generate does not create any files" {
101+
jq '.canisters.hello_backend.declarations.bindings=[]' dfx.json | sponge dfx.json
102+
103+
dfx_start
104+
dfx canister create --all
105+
dfx build
106+
dfx canister install --all
107+
108+
dfx generate
109+
110+
assert_file_not_exists "src/declarations/hello_backend/hello_backend.did"
111+
assert_file_not_exists "src/declarations/hello_backend/hello_backend.did.js"
112+
assert_file_not_exists "src/declarations/hello_backend/hello_backend.did.d.ts"
113+
assert_file_not_exists "src/declarations/hello_backend/index.js"
114+
assert_file_not_exists "src/declarations/hello_backend/index.d.ts"
115+
}

src/dfx/assets/language_bindings/index.d.ts.hbs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import { ActorSubclass, HttpAgentOptions, ActorConfig } from '@dfinity/agent';
22
import { Principal } from '@dfinity/principal';
3+
import { IDL } from '@dfinity/candid';
34

45
import { _SERVICE } from './{{canister_name}}.did';
56

7+
export declare const idlFactory: IDL.InterfaceFactory;
8+
export declare const canisterId: string;
9+
610
export declare interface CreateActorOptions {
711
agentOptions?: HttpAgentOptions;
812
actorOptions?: ActorConfig;

src/dfx/src/lib/builders/mod.rs

Lines changed: 96 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -118,16 +118,6 @@ pub trait CanisterBuilder {
118118
)
119119
})?;
120120
}
121-
std::fs::create_dir_all(&generate_output_dir).with_context(|| {
122-
format!(
123-
"Failed to create dir: {}",
124-
generate_output_dir.to_string_lossy()
125-
)
126-
})?;
127-
128-
let generated_idl_path = self.generate_idl(pool, info, config)?;
129-
130-
let (env, ty) = check_candid_file(generated_idl_path.as_path())?;
131121

132122
let bindings = info
133123
.get_declarations_config()
@@ -145,6 +135,17 @@ pub trait CanisterBuilder {
145135
);
146136
}
147137

138+
std::fs::create_dir_all(&generate_output_dir).with_context(|| {
139+
format!(
140+
"Failed to create dir: {}",
141+
generate_output_dir.to_string_lossy()
142+
)
143+
})?;
144+
145+
let generated_idl_path = self.generate_idl(pool, info, config)?;
146+
147+
let (env, ty) = check_candid_file(generated_idl_path.as_path())?;
148+
148149
// Typescript
149150
if bindings.contains(&"ts".to_string()) {
150151
let output_did_ts_path = generate_output_dir
@@ -158,6 +159,8 @@ pub trait CanisterBuilder {
158159
)
159160
})?;
160161
eprintln!(" {}", &output_did_ts_path.display());
162+
163+
compile_handlebars_files("ts", info, generate_output_dir)?;
161164
}
162165

163166
// Javascript
@@ -174,78 +177,8 @@ pub trait CanisterBuilder {
174177
)
175178
})?;
176179
eprintln!(" {}", &output_did_js_path.display());
177-
// index.js
178-
let mut language_bindings = crate::util::assets::language_bindings()
179-
.context("Failed to get language bindings archive.")?;
180-
for f in language_bindings
181-
.entries()
182-
.context("Failed to read language bindings archive entries.")?
183-
{
184-
let mut file = f.context("Failed to read language bindings archive entry.")?;
185-
186-
let pathname: PathBuf = file
187-
.path()
188-
.context("Failed to read language bindings entry path name.")?
189-
.to_path_buf();
190-
let extension = pathname.extension();
191-
let is_template = matches! (extension, Some (ext ) if ext == OsStr::new("hbs"));
192-
193-
if is_template {
194-
let mut file_contents = String::new();
195-
file.read_to_string(&mut file_contents)
196-
.context("Failed to read language bindings archive file content.")?;
197-
198-
// create the handlebars registry
199-
let handlebars = Handlebars::new();
200-
201-
let mut data: BTreeMap<String, &String> = BTreeMap::new();
202-
203-
let canister_name = &info.get_name().to_string();
204-
205-
let node_compatibility = info.get_declarations_config().node_compatibility;
206-
207-
// Insert only if node outputs are specified
208-
let actor_export = if node_compatibility {
209-
// leave empty for nodejs
210-
"".to_string()
211-
} else {
212-
format!(
213-
r#"
214180

215-
/**
216-
* A ready-to-use agent for the {0} canister
217-
* @type {{import("@dfinity/agent").ActorSubclass<import("./{0}.did.js")._SERVICE>}}
218-
*/
219-
export const {0} = createActor(canisterId);"#,
220-
canister_name
221-
)
222-
.to_string()
223-
};
224-
225-
data.insert("canister_name".to_string(), canister_name);
226-
data.insert("actor_export".to_string(), &actor_export);
227-
228-
let process_string: String = match &info.get_declarations_config().env_override
229-
{
230-
Some(s) => format!(r#""{}""#, s.clone()),
231-
None => {
232-
format!(
233-
"process.env.{}{}",
234-
&canister_name.to_ascii_uppercase(),
235-
"_CANISTER_ID"
236-
)
237-
}
238-
};
239-
240-
data.insert("canister_name_process_env".to_string(), &process_string);
241-
242-
let new_file_contents =
243-
handlebars.render_template(&file_contents, &data).unwrap();
244-
let new_path = generate_output_dir.join(pathname.with_extension(""));
245-
std::fs::write(&new_path, new_file_contents)
246-
.with_context(|| format!("Failed to write to {}.", new_path.display()))?;
247-
}
248-
}
181+
compile_handlebars_files("js", info, generate_output_dir)?;
249182
}
250183

251184
// Motoko
@@ -268,6 +201,7 @@ export const {0} = createActor(canisterId);"#,
268201
} else {
269202
eprintln!(" {}", &generated_idl_path.display());
270203
}
204+
271205
Ok(())
272206
}
273207

@@ -281,6 +215,87 @@ export const {0} = createActor(canisterId);"#,
281215
}
282216
}
283217

218+
fn compile_handlebars_files(
219+
lang: &str,
220+
info: &CanisterInfo,
221+
generate_output_dir: &Path,
222+
) -> DfxResult {
223+
// index.js
224+
let mut language_bindings = crate::util::assets::language_bindings()
225+
.context("Failed to get language bindings archive.")?;
226+
for f in language_bindings
227+
.entries()
228+
.context("Failed to read language bindings archive entries.")?
229+
{
230+
let mut file = f.context("Failed to read language bindings archive entry.")?;
231+
232+
let pathname: PathBuf = file
233+
.path()
234+
.context("Failed to read language bindings entry path name.")?
235+
.to_path_buf();
236+
let file_extension = format!("{}.hbs", lang);
237+
let is_template = pathname
238+
.to_str()
239+
.map_or(false, |name| name.ends_with(&file_extension));
240+
241+
if is_template {
242+
let mut file_contents = String::new();
243+
file.read_to_string(&mut file_contents)
244+
.context("Failed to read language bindings archive file content.")?;
245+
246+
// create the handlebars registry
247+
let handlebars = Handlebars::new();
248+
249+
let mut data: BTreeMap<String, &String> = BTreeMap::new();
250+
251+
let canister_name = &info.get_name().to_string();
252+
253+
let node_compatibility = info.get_declarations_config().node_compatibility;
254+
255+
// Insert only if node outputs are specified
256+
let actor_export = if node_compatibility {
257+
// leave empty for nodejs
258+
"".to_string()
259+
} else {
260+
format!(
261+
r#"
262+
263+
/**
264+
* A ready-to-use agent for the {0} canister
265+
* @type {{import("@dfinity/agent").ActorSubclass<import("./{0}.did.js")._SERVICE>}}
266+
*/
267+
export const {0} = createActor(canisterId);"#,
268+
canister_name
269+
)
270+
.to_string()
271+
};
272+
273+
data.insert("canister_name".to_string(), canister_name);
274+
data.insert("actor_export".to_string(), &actor_export);
275+
276+
let process_string: String = match &info.get_declarations_config().env_override {
277+
Some(s) => format!(r#""{}""#, s.clone()),
278+
None => {
279+
format!(
280+
"process.env.{}{}",
281+
&canister_name.to_ascii_uppercase(),
282+
"_CANISTER_ID"
283+
)
284+
}
285+
};
286+
287+
data.insert("canister_name_process_env".to_string(), &process_string);
288+
289+
let new_file_contents = handlebars.render_template(&file_contents, &data).unwrap();
290+
let new_path = generate_output_dir.join(pathname.with_extension(""));
291+
std::fs::write(&new_path, new_file_contents)
292+
.with_context(|| format!("Failed to write to {}.", new_path.display()))?;
293+
}
294+
}
295+
296+
Ok(())
297+
}
298+
284299
// TODO: this function was copied from src/lib/models/canister.rs
285300
fn ensure_trailing_newline(s: String) -> String {
286301
if s.ends_with('\n') {

0 commit comments

Comments
 (0)