Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 53 additions & 6 deletions src/core/generate/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { basename, resolve } from 'node:path';
import { prepareBinding } from './bindings.ts';
import { ensureDir, writeFileSafe } from './fs.ts';
import type { DidFile } from './rs/dist/icp-js-bindgen';
import { type WasmGenerateResult, wasmGenerate, wasmInit } from './rs.ts';

const DID_FILE_EXTENSION = '.did';
Expand Down Expand Up @@ -59,7 +60,11 @@ export type GenerateOptions = {
/**
* The path to the `.did` file.
*/
didFile: string;
didFile?: string;
/**
*
*/
didRemoteUrl?: string;
Comment on lines +63 to +67
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we keep only one argument here and specify in the doc that it can be a local path or remote URL? Then, we can simply check in the implementation if it starts with http or https and perform the fetch

/**
* The path to the directory where the bindings will be generated.
*/
Expand Down Expand Up @@ -93,6 +98,7 @@ export async function generate(options: GenerateOptions) {

const {
didFile,
didRemoteUrl,
outDir,
output = {
force: false,
Expand All @@ -108,15 +114,56 @@ export async function generate(options: GenerateOptions) {
const force = Boolean(output.force); // ensure force is a boolean
const declarationsRootExports = Boolean(output.declarations?.rootExports ?? false); // ensure rootExports is a boolean

const didFilePath = resolve(didFile);
const outputFileName = basename(didFile, DID_FILE_EXTENSION);
if (didFile && didRemoteUrl) {
throw new Error('Only one of didFile or didRemoteUrl should be provided.');
}

function fromDidFile(didFile: string): {
did_file: DidFile;
service_name: string;
} {
return {
did_file: { LocalPath: resolve(didFile) },
service_name: basename(didFile, DID_FILE_EXTENSION),
};
}

async function fromDidRemoteUrl(didRemoteUrl: string): Promise<{
did_file: DidFile;
service_name: string;
}> {
const u = new URL(didRemoteUrl);
const fileName = u.pathname.split('/').pop();
const r = await fetch(didRemoteUrl);
if (!r.ok) {
throw new Error(
`Failed to fetch .did file from URL: ${didRemoteUrl}. Status: ${r.status} ${r.statusText}`,
);
}
const didFile = await r.text();
return {
did_file: { InlineString: didFile },
service_name: fileName || 'service',
};
}

let did_file: DidFile;
let service_name: string;

if (didFile) {
({ did_file, service_name } = fromDidFile(didFile));
} else if (didRemoteUrl) {
({ did_file, service_name } = await fromDidRemoteUrl(didRemoteUrl));
} else {
throw new Error('Either didFile or didRemoteUrl must be provided.');
}

await ensureDir(outDir);
await ensureDir(resolve(outDir, 'declarations'));

const result = wasmGenerate({
did_file_path: didFilePath,
service_name: outputFileName,
did_file,
service_name,
declarations: {
root_exports: declarationsRootExports,
},
Expand All @@ -125,7 +172,7 @@ export async function generate(options: GenerateOptions) {
await writeBindings({
bindings: result,
outDir,
outputFileName,
outputFileName: service_name,
output,
force,
});
Expand Down
20 changes: 17 additions & 3 deletions src/core/generate/rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,17 @@ pub struct GenerateDeclarationsOptions {
pub root_exports: bool,
}

#[derive(Tsify, Deserialize)]
#[tsify(from_wasm_abi)]
pub enum DidFile {
LocalPath(String),
InlineString(String),
}

#[derive(Tsify, Deserialize)]
#[tsify(from_wasm_abi)]
pub struct GenerateOptions {
pub did_file_path: String,
pub did_file: DidFile,
pub service_name: String,
pub declarations: GenerateDeclarationsOptions,
}
Expand All @@ -44,8 +51,15 @@ pub struct GenerateResult {

#[wasm_bindgen]
pub fn generate(options: GenerateOptions) -> Result<GenerateResult, JsError> {
let input_path = PathBuf::from(options.did_file_path);
let (env, actor, prog) = parser::check_file(input_path.as_path()).map_err(JsError::from)?;
let (env, actor, prog) = match options.did_file {
DidFile::LocalPath(did_file_path) => {
let input_path = PathBuf::from(did_file_path);
parser::check_file(input_path.as_path()).map_err(JsError::from)?
}
DidFile::InlineString(did_file_str) => {
parser::check_str(&did_file_str).map_err(JsError::from)?
}
};

let declarations_js = javascript::compile(&env, &actor, options.declarations.root_exports);
let declarations_ts =
Expand Down
12 changes: 12 additions & 0 deletions src/core/generate/rs/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,3 +273,15 @@ pub fn check_file(file: &Path) -> Result<(TypeEnv, Option<Type>, IDLMergedProg)>
let res = check_actor(&env, &merged_prog.resolve_actor()?)?;
Ok((te, res, merged_prog))
}

pub fn check_str(str: &String) -> Result<(TypeEnv, Option<Type>, IDLMergedProg)> {
let prog = IDLMergedProg::new(pretty_parse::<IDLProg>(&String::new(), str)?);
let mut te = TypeEnv::new();
let mut env = Env {
te: &mut te,
pre: false,
};
check_decs(&mut env, &prog.decs())?;
let res = check_actor(&env, &prog.resolve_actor()?)?;
Ok((te, res, prog))
}
7 changes: 6 additions & 1 deletion src/plugins/vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ export function icpBindgen(options: Options): Plugin {
}

function watchDidFileChanges(server: ViteDevServer, options: Options) {
if (!options.didFile) {
return;
}

const didFilePath = resolve(options.didFile);

server.watcher.add(didFilePath);
Expand All @@ -115,7 +119,8 @@ function watchDidFileChanges(server: ViteDevServer, options: Options) {
}

async function run(options: Options) {
console.log(cyan(`[${VITE_PLUGIN_NAME}] Generating bindings from`), green(options.didFile));
const source = options.didFile || options.didRemoteUrl || 'unknown source';
console.log(cyan(`[${VITE_PLUGIN_NAME}] Generating bindings from`), green(source));

await generate({
didFile: options.didFile,
Expand Down
4 changes: 2 additions & 2 deletions tests/wasm-generate.test.ts
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add tests for remote URLs here as well, but I can do it in a follow-up PR

Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('wasmGenerate', () => {
const snapshotsDir = `${SNAPSHOTS_BASE_DIR}/no-root-export`;

const result = wasmGenerate({
did_file_path: didFile,
did_file: { LocalPath: didFile },
service_name: serviceName,
declarations: {
root_exports: false,
Expand All @@ -43,7 +43,7 @@ describe('wasmGenerate', () => {
const snapshotsDir = `${SNAPSHOTS_BASE_DIR}/root-export`;

const result = wasmGenerate({
did_file_path: didFile,
did_file: { LocalPath: didFile },
service_name: serviceName,
declarations: {
root_exports: true,
Expand Down
Loading