diff --git a/.github/actions/extend-space/action.yaml b/.github/actions/extend-space/action.yaml index 09d316a3a..36b34f48d 100644 --- a/.github/actions/extend-space/action.yaml +++ b/.github/actions/extend-space/action.yaml @@ -1,6 +1,5 @@ # Extends disk space on github hosted runners - name: "Extend space" description: "Teases out as much free space as possible" @@ -58,4 +57,4 @@ runs: -o lowerdir=${HOME}_lower,upperdir=$EXTENDED_PATH/home/upper,workdir=$EXTENDED_PATH/home/work \ $HOME sudo chown $(id -u):$(id -g) $HOME - df -h \ No newline at end of file + df -h diff --git a/.github/workflows/check-offline.yaml b/.github/workflows/check-offline.yaml deleted file mode 100644 index 7d116b091..000000000 --- a/.github/workflows/check-offline.yaml +++ /dev/null @@ -1,30 +0,0 @@ -name: "check-lockfile" -on: - # Trigger the workflow on push or pull request, - # but only for the main branch - push: - branches: [ main, develop, holochain-0.1, holochain-0.2 ] - pull_request: - branches: [ main, develop, holochain-0.1, holochain-0.2 ] - -jobs: - check-lockfile: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Install nix - uses: cachix/install-nix-action@v18 - with: - install_url: https://releases.nixos.org/nix/nix-2.12.0/install - extra_nix_config: | - experimental-features = flakes nix-command - - - uses: cachix/cachix-action@v10 - with: - name: holochain-ci - - - name: Install - run: | - nix develop --command bash -c "cargo build --locked" - diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 1f4352810..233a4b31b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -15,7 +15,6 @@ jobs: - name: Extend space uses: ./.github/actions/extend-space - - name: Install nix uses: cachix/install-nix-action@v23 with: diff --git a/.gitignore b/.gitignore index 89a98ca76..971de4a60 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,4 @@ yarn-error.log* *.sln *.sw? result* -.cargo \ No newline at end of file +.cargo diff --git a/Cargo.lock b/Cargo.lock index 9a1e5fc74..64e0bf211 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1703,6 +1703,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "errno" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.45.0", +] + [[package]] name = "errno-dragonfly" version = "0.1.2" @@ -2684,6 +2695,7 @@ dependencies = [ "holochain_util", "ignore", "include_dir", + "itertools 0.10.5", "json_value_merge", "mr_bundle", "path-clean", @@ -4006,6 +4018,12 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +[[package]] +name = "linux-raw-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd550e73688e6d578f0ac2119e32b797a327631a42f9433e59d02e139c8df60d" + [[package]] name = "lock_api" version = "0.3.4" diff --git a/Cargo.toml b/Cargo.toml index f2c4b19ae..27f50dbd5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ serde = "1" json_value_merge = "1.1.2" temp-dir = "0.1" semver = "1.0" +itertools = "0.10" [dev-dependencies] assert_cmd = "1.0" diff --git a/README.md b/README.md index 4c8d51193..3e7a14a54 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,16 @@ CLI to easily generate and edit holochain apps. The easiest way to start using the scaffolding tool is through holonix: ```bash -nix-shell https://holochain.love -hc-scaffold --version +nix run github:holochain/holochain#hc-scaffold -- --version ``` Should print the version of the scaffolding tool. ## Usage -These are the commands that you can run with the scaffolding tool: +Refer to [the holochain developer instructions](https://developer.holochain.org/get-building/) to know how you can use the scaffolding tool to create your own apps. + +These are the commands that you can run with the scaffolding tool inside of a holonix develop shell: ```bash # Scaffold an example app diff --git a/flake.lock b/flake.lock index 5d955675e..7de0b0392 100644 --- a/flake.lock +++ b/flake.lock @@ -207,11 +207,11 @@ ] }, "locked": { - "lastModified": 1695063947, - "narHash": "sha256-zjMJ0vEV37BupAkTfhT4E+/fjhYI9mt/XdnlUwrUd4U=", + "lastModified": 1695190832, + "narHash": "sha256-zLKW0gt0XBHlUWf/OHnNZrBb1ufYZVVJ9VKAtx0UP+E=", "owner": "holochain", "repo": "holochain", - "rev": "2e98f34947005482472cb64f8d6c9a59d0cc941c", + "rev": "281bad89f2f712e7397d038047f02ed8174aa06a", "type": "github" }, "original": { @@ -288,11 +288,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1694767346, - "narHash": "sha256-5uH27SiVFUwsTsqC5rs3kS7pBoNhtoy9QfTP9BmknGk=", + "lastModified": 1694959747, + "narHash": "sha256-CXQ2MuledDVlVM5dLC4pB41cFlBWxRw4tCBsFrq3cRk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ace5093e36ab1e95cb9463863491bee90d5a4183", + "rev": "970a59bd19eff3752ce552935687100c46e820a5", "type": "github" }, "original": { @@ -393,11 +393,11 @@ ] }, "locked": { - "lastModified": 1695003086, - "narHash": "sha256-d1/ZKuBRpxifmUf7FaedCqhy0lyVbqj44Oc2s+P5bdA=", + "lastModified": 1695175880, + "narHash": "sha256-TBR5/K3jkrd+U5mjxvRvUhlcT1Hw9jFywz1TjAGZRm4=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "b87a14abea512d956f0b89d0d8a1e9b41f3e20ff", + "rev": "e054ca37ee416efe9d8fc72d249ec332ef74b6d4", "type": "github" }, "original": { @@ -409,11 +409,11 @@ "scaffolding": { "flake": false, "locked": { - "lastModified": 1692147670, - "narHash": "sha256-jFt4LTUaUZTiOg2DLlbUqyeV2edfUxiJSljRjVJlObE=", + "lastModified": 1695069964, + "narHash": "sha256-QtX2sZgBZ6pxtPrJp9RslJD0LU1KILp+Y0OVldapImA=", "owner": "holochain", "repo": "scaffolding", - "rev": "8a63d356a0856643769adc567e078796c6509511", + "rev": "f0b878cdc75bb7b2748cb5f1564d4a4632504ca8", "type": "github" }, "original": { @@ -447,11 +447,11 @@ }, "locked": { "dir": "versions/0_2", - "lastModified": 1695063947, - "narHash": "sha256-zjMJ0vEV37BupAkTfhT4E+/fjhYI9mt/XdnlUwrUd4U=", + "lastModified": 1695190832, + "narHash": "sha256-zLKW0gt0XBHlUWf/OHnNZrBb1ufYZVVJ9VKAtx0UP+E=", "owner": "holochain", "repo": "holochain", - "rev": "2e98f34947005482472cb64f8d6c9a59d0cc941c", + "rev": "281bad89f2f712e7397d038047f02ed8174aa06a", "type": "github" }, "original": { diff --git a/run_test.sh b/run_test.sh index fb9b1d38d..0fcb14db9 100755 --- a/run_test.sh +++ b/run_test.sh @@ -1,6 +1,18 @@ #!/usr/bin/env bash set -e +rm -rf /tmp/hello-world +cd /tmp + +hc-scaffold example hello-world +cd hello-world + +nix develop --command bash -c " +set -e +npm i +npm t +" + rm -rf /tmp/forum-svelte cd /tmp diff --git a/src/cli.rs b/src/cli.rs index 65c0b4616..5d8143bdd 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,7 +1,5 @@ use crate::error::{ScaffoldError, ScaffoldResult}; -use crate::file_tree::{ - dir_content, file_content, insert_file, load_directory_into_memory, FileTree, -}; +use crate::file_tree::{dir_content, file_content, load_directory_into_memory, FileTree}; use crate::scaffold::app::cargo::exec_metadata; use crate::scaffold::app::nix::setup_nix_developer_environment; use crate::scaffold::app::AppFileTree; @@ -72,6 +70,9 @@ pub enum HcScaffold { /// If "--templates-url" is given, the template must be located at the ".templates/" folder of the repository /// If not, the template must be an option from the built-in templates: "vanilla", "vue", "lit", "svelte" template: Option, + + #[structopt(long = "holo", hidden = true)] + holo_enabled: bool, }, /// Set up the template used in this project Template(HcScaffoldTemplate), @@ -228,6 +229,7 @@ impl HcScaffold { template, templates_url, templates_path, + holo_enabled, } => { let prompt = String::from("App name (no whitespaces):"); let name: String = match name { @@ -305,6 +307,7 @@ impl HcScaffold { &template_file_tree, template_name, scaffold_template, + holo_enabled, )?; let file_tree = MergeableFileSystemTree::::from(dir! { @@ -733,69 +736,7 @@ Collection "{}" scaffolded! &template_file_tree, template_name.clone(), false, - )?; - - // scaffold dna hello_world - let dna_name = String::from("hello_world"); - - let app_file_tree = - AppFileTree::get_or_choose(file_tree, &Some(name.clone()))?; - let ScaffoldedTemplate { file_tree, .. } = - scaffold_dna(app_file_tree, &template_file_tree, &dna_name)?; - - // scaffold integrity zome hello_world - let dna_file_tree = - DnaFileTree::get_or_choose(file_tree, &Some(dna_name.clone()))?; - let dna_manifest_path = dna_file_tree.dna_manifest_path.clone(); - - let integrity_zome_name = String::from("hello_world_integrity"); - let integrity_zome_path = PathBuf::new() - .join("dnas") - .join(&dna_name) - .join("zomes") - .join("integrity"); - let ScaffoldedTemplate { file_tree, .. } = - scaffold_integrity_zome_with_path( - dna_file_tree, - &template_file_tree, - &integrity_zome_name, - &integrity_zome_path, - )?; - - // scaffold integrity zome hello_world - let dna_file_tree = - DnaFileTree::from_dna_manifest_path(file_tree, &dna_manifest_path)?; - - let coordinator_zome_name = String::from("hello_world"); - let coordinator_zome_path = PathBuf::new() - .join("dnas") - .join(dna_name) - .join("zomes") - .join("coordinator"); - let ScaffoldedTemplate { mut file_tree, .. } = - scaffold_coordinator_zome_in_path( - dna_file_tree, - &template_file_tree, - &coordinator_zome_name, - &Some(vec![integrity_zome_name]), - &coordinator_zome_path, - )?; - - // Add "hello_world" function to coordinator - let hello_world_zome = format!( - r#"use hdk::prelude::*; - -#[hdk_extern] -pub fn hello_world(_: ()) -> ExternResult {{ - Ok(String::from("hello world from a Holochain app!")) -}} -"# - ); - - insert_file( - &mut file_tree, - &coordinator_zome_path.join("hello_world/src/lib.rs"), - &hello_world_zome, + false, )?; file_tree @@ -809,6 +750,7 @@ pub fn hello_world(_: ()) -> ExternResult {{ &template_file_tree, template_name.clone(), false, + false, )?; // scaffold dna hello_world diff --git a/src/reserved_words.rs b/src/reserved_words.rs index eb0470f91..d526a98a8 100644 --- a/src/reserved_words.rs +++ b/src/reserved_words.rs @@ -2,7 +2,7 @@ use convert_case::{Case, Casing}; use crate::error::{ScaffoldError, ScaffoldResult}; -const RESERVED_WORDS: [&str; 26] = [ +const RESERVED_WORDS: [&str; 27] = [ "type", "role", "enum", @@ -29,6 +29,7 @@ const RESERVED_WORDS: [&str; 26] = [ "EntryHash", "ActionHash", "AgentPubKey", + "Call", ]; // Returns an error if the given string is invalid due to it being a reserved word diff --git a/src/scaffold/entry_type.rs b/src/scaffold/entry_type.rs index c4b040eb8..4fc148bf7 100644 --- a/src/scaffold/entry_type.rs +++ b/src/scaffold/entry_type.rs @@ -208,10 +208,10 @@ pub fn scaffold_entry_type( )?; } - let zome_file_tree = + let mut zome_file_tree = ZomeFileTree::from_zome_manifest(zome_file_tree.dna_file_tree, coordinator_zome.clone())?; - let zome_file_tree = add_crud_functions_to_coordinator( + zome_file_tree = add_crud_functions_to_coordinator( zome_file_tree, &integrity_zome_name, &entry_def, diff --git a/src/scaffold/entry_type/fields.rs b/src/scaffold/entry_type/fields.rs index 0a347be57..329647a0a 100644 --- a/src/scaffold/entry_type/fields.rs +++ b/src/scaffold/entry_type/fields.rs @@ -220,7 +220,9 @@ pub fn choose_field( let maybe_linked_from = match &field_type { FieldType::AgentPubKey => { let link_from = Confirm::with_theme(&ColorfulTheme::default()) - .with_prompt("Should a link from this field be created when this entry is created?") + .with_prompt( + "Should a link from the AgentPubKey provided in this field also be created when entries of this type are created?" + ) .interact()?; match link_from { @@ -234,55 +236,51 @@ pub fn choose_field( } } FieldType::ActionHash | FieldType::EntryHash => { - let all_entry_types = get_all_entry_types(zome_file_tree)?.unwrap_or(vec![]); - if all_entry_types.len() == 0 { - None - } else { - let link_from = Confirm::with_theme(&ColorfulTheme::default()) + let link_from = Confirm::with_theme(&ColorfulTheme::default()) .with_prompt( - "Should a link from this field be created when this entry is created?", + format!( + "Should a link from the {} provided in this field also be created when entries of this type are created?", + field_type.to_string()) ) .interact()?; - match link_from { - false => None, - true => { - let mut all_options: Vec = all_entry_types - .clone() - .into_iter() - .map(|r| r.entry_type) - .collect(); - - if let Cardinality::Option | Cardinality::Vector = cardinality { - all_options.push(format!( - "{} (itself)", - entry_type_name.to_case(Case::Pascal) - )); - } - - let selection = Select::with_theme(&ColorfulTheme::default()) - .with_prompt(String::from( - "Which entry type is this field referring to?", - )) - .default(0) - .items(&all_options[..]) - .interact()?; - - let reference_entry_hash = match field_type { - FieldType::EntryHash => true, - _ => false, - }; - - match selection == all_entry_types.len() { - true => Some(Referenceable::EntryType(EntryTypeReference { - entry_type: entry_type_name.clone(), - reference_entry_hash, - })), - false => Some(Referenceable::EntryType(EntryTypeReference { - entry_type: all_entry_types[selection].entry_type.clone(), - reference_entry_hash, - })), - } + match link_from { + false => None, + true => { + let all_entry_types = get_all_entry_types(zome_file_tree)?.unwrap_or(vec![]); + let mut all_options: Vec = all_entry_types + .clone() + .into_iter() + .map(|r| r.entry_type) + .collect(); + + if let Cardinality::Option | Cardinality::Vector = cardinality { + all_options.push(format!( + "{} (itself)", + entry_type_name.to_case(Case::Pascal) + )); + } + + let selection = Select::with_theme(&ColorfulTheme::default()) + .with_prompt(String::from("Which entry type is this field referring to?")) + .default(0) + .items(&all_options[..]) + .interact()?; + + let reference_entry_hash = match field_type { + FieldType::EntryHash => true, + _ => false, + }; + + match selection == all_entry_types.len() { + true => Some(Referenceable::EntryType(EntryTypeReference { + entry_type: entry_type_name.clone(), + reference_entry_hash, + })), + false => Some(Referenceable::EntryType(EntryTypeReference { + entry_type: all_entry_types[selection].entry_type.clone(), + reference_entry_hash, + })), } } } diff --git a/src/scaffold/entry_type/integrity.rs b/src/scaffold/entry_type/integrity.rs index bbb5d2e84..a3612a301 100644 --- a/src/scaffold/entry_type/integrity.rs +++ b/src/scaffold/entry_type/integrity.rs @@ -1,5 +1,5 @@ use convert_case::{Case, Casing}; -use holochain::test_utils::itertools::Itertools; +use itertools::Itertools; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use std::{ffi::OsString, path::PathBuf}; diff --git a/src/scaffold/link_type/integrity.rs b/src/scaffold/link_type/integrity.rs index bd5905801..91467e629 100644 --- a/src/scaffold/link_type/integrity.rs +++ b/src/scaffold/link_type/integrity.rs @@ -1,7 +1,7 @@ use std::{ffi::OsString, path::PathBuf}; use convert_case::{Case, Casing}; -use holochain::test_utils::itertools::Itertools; +use itertools::Itertools; use proc_macro2::TokenStream; use quote::{format_ident, quote}; @@ -427,15 +427,18 @@ fn signal_link_types_variants() -> ScaffoldResult> { fn signal_action_match_arms() -> ScaffoldResult> { Ok(vec![ - syn::parse_str::("Action::CreateLink(create_link) => { + syn::parse_str::( + "Action::CreateLink(create_link) => { if let Ok(Some(link_type)) = LinkTypes::from_type(create_link.zome_index, create_link.link_type) { emit_signal(Signal::LinkCreated { action, link_type })?; } Ok(()) - }")?, - syn::parse_str::("Action::DeleteLink(delete_link) => { + }", + )?, + syn::parse_str::( + "Action::DeleteLink(delete_link) => { let record = get(delete_link.link_add_address.clone(), GetOptions::default())?.ok_or( wasm_error!(WasmErrorInner::Guest( \"Failed to fetch CreateLink action\".to_string() @@ -456,7 +459,8 @@ fn signal_action_match_arms() -> ScaffoldResult> { ))); } } - }")? + }", + )?, ]) } diff --git a/src/scaffold/web_app.rs b/src/scaffold/web_app.rs index 4afb1ab2c..65ec37020 100644 --- a/src/scaffold/web_app.rs +++ b/src/scaffold/web_app.rs @@ -23,6 +23,7 @@ fn web_app_skeleton( template_file_tree: &FileTree, template_name: String, scaffold_template: bool, + holo_enabled: bool, ) -> ScaffoldResult { check_for_reserved_words(&app_name)?; @@ -53,7 +54,7 @@ fn web_app_skeleton( } let mut scaffold_template_result = - scaffold_web_app_template(app_file_tree, &template_file_tree, &app_name)?; + scaffold_web_app_template(app_file_tree, &template_file_tree, &app_name, holo_enabled)?; scaffold_template_result .file_tree @@ -71,6 +72,7 @@ pub fn scaffold_web_app( template_file_tree: &FileTree, template_name: String, scaffold_template: bool, + holo_enabled: bool, ) -> ScaffoldResult { let scaffolded_template = web_app_skeleton( app_name.clone(), @@ -79,6 +81,7 @@ pub fn scaffold_web_app( &template_file_tree, template_name, scaffold_template, + holo_enabled, )?; Ok(ScaffoldedTemplate { file_tree: scaffolded_template.file_tree, diff --git a/src/scaffold/zome/coordinator.rs b/src/scaffold/zome/coordinator.rs index 108f5a0f0..eb1229859 100644 --- a/src/scaffold/zome/coordinator.rs +++ b/src/scaffold/zome/coordinator.rs @@ -56,7 +56,6 @@ pub fn initial_lib_rs(dependencies: &Option>) -> String { s } }; - format!( r#"use hdk::prelude::*; {integrity_imports} diff --git a/src/templates/web_app.rs b/src/templates/web_app.rs index f3e9ccb53..aa7187d35 100644 --- a/src/templates/web_app.rs +++ b/src/templates/web_app.rs @@ -22,12 +22,14 @@ pub struct ScaffoldWebAppData { pub hdi_version: String, pub holochain_client_version: String, pub tryorama_version: String, + pub holo_enabled: bool, } pub fn scaffold_web_app_template( mut app_file_tree: FileTree, template_file_tree: &FileTree, app_name: &String, + holo_enabled: bool, ) -> ScaffoldResult { let data = ScaffoldWebAppData { app_name: app_name.clone(), @@ -36,6 +38,7 @@ pub fn scaffold_web_app_template( hdi_version: hdi_version(), holochain_client_version: holochain_client_version(), tryorama_version: tryorama_version(), + holo_enabled, }; let h = build_handlebars(template_file_tree)?; diff --git a/src/versions.rs b/src/versions.rs index 8fdfaccd5..86c8cbdad 100644 --- a/src/versions.rs +++ b/src/versions.rs @@ -6,6 +6,10 @@ pub fn holochain_client_version() -> String { String::from("^0.15.0") } +pub fn web_sdk_version() -> String { + String::from("^0.6.10-prerelease") +} + pub fn hdi_version() -> String { holochain::HDI_VERSION.to_string() } diff --git a/templates/lit/web-app/package.json.hbs b/templates/lit/web-app/package.json.hbs index 62060d4b2..c16b5d3e1 100644 --- a/templates/lit/web-app/package.json.hbs +++ b/templates/lit/web-app/package.json.hbs @@ -10,6 +10,11 @@ "network": "hc s clean && npm run build:happ && UI_PORT=8888 concurrently \"npm start -w ui\" \"npm run launch:happ\" \"holochain-playground\"", "test": "npm run build:zomes && hc app pack workdir --recursive && npm t -w tests", "launch:happ": "concurrently \"hc run-local-services --bootstrap-port $BOOTSTRAP_PORT --signal-port $SIGNAL_PORT\" \"echo pass | RUST_LOG=warn hc launch --piped -n $AGENTS workdir/{{app_name}}.happ --ui-port $UI_PORT network --bootstrap http://127.0.0.1:\"$BOOTSTRAP_PORT\" webrtc ws://127.0.0.1:\"$SIGNAL_PORT\"\"", + {{#if holo_enabled}} + "start:holo": "AGENTS=2 npm run network:holo", + "network:holo": "npm run build:happ && UI_PORT=8888 concurrently \"npm run launch:holo-dev-server\" \"holochain-playground ws://localhost:4444\" \"concurrently-repeat 'VITE_APP_CHAPERONE_URL=http://localhost:24274 VITE_APP_IS_HOLO=true npm start -w ui' $AGENTS\"", + "launch:holo-dev-server": "holo-dev-server workdir/{{app_name}}.happ", + {{/if}} "package": "npm run build:happ && npm run package -w ui && hc web-app pack workdir --recursive", "build:happ": "npm run build:zomes && hc app pack workdir --recursive", "build:zomes": "RUSTFLAGS='' CARGO_TARGET_DIR=target cargo build --release --target wasm32-unknown-unknown" @@ -19,6 +24,9 @@ "concurrently": "^6.2.1", "rimraf": "^3.0.2", "new-port-cli": "^1.0.0" + {{#if holo_enabled}} + "concurrently-repeat": "^0.0.1", + {{/if}} }, "engines": { "npm": ">=7.0.0" diff --git a/templates/lit/web-app/ui/package.json.hbs b/templates/lit/web-app/ui/package.json.hbs index dbbcab476..d27c18ad6 100644 --- a/templates/lit/web-app/ui/package.json.hbs +++ b/templates/lit/web-app/ui/package.json.hbs @@ -10,6 +10,9 @@ }, "dependencies": { "@holochain/client": "{{holochain_client_version}}", + {{#if holo_enabled}} + "@holo-host/web-sdk": "{{web_sdk_version}}", + {{/if}} "@lit-labs/context": "^0.2.0", "@lit-labs/task": "^2.0.0", "@material/mwc-circular-progress": "^0.27.0", diff --git a/templates/lit/web-app/ui/src/holochain-app.ts.hbs b/templates/lit/web-app/ui/src/holochain-app.ts.hbs index 09562134f..8c25d51e7 100644 --- a/templates/lit/web-app/ui/src/holochain-app.ts.hbs +++ b/templates/lit/web-app/ui/src/holochain-app.ts.hbs @@ -5,11 +5,22 @@ import { ActionHash, AppAgentClient, } from '@holochain/client'; +{{#if holo_enabled}} +import WebSdk from '@holo-host/web-sdk' +import type { AgentState } from '@holo-host/web-sdk'; +{{/if}} import { provide } from '@lit-labs/context'; import '@material/mwc-circular-progress'; +{{#if holo_enabled}} +import '@material/mwc-button'; +{{/if}} import { clientContext } from './contexts'; +{{#if holo_enabled}} +const IS_HOLO = ['true', '1', 't'].includes(import.meta.env.VITE_APP_IS_HOLO?.toLowerCase()) +{{/if}} + @customElement('holochain-app') export class HolochainApp extends LitElement { @state() loading = true; @@ -19,11 +30,40 @@ export class HolochainApp extends LitElement { client!: AppAgentClient; async firstUpdated() { + {{#if holo_enabled}} + if (IS_HOLO) { + const client = await WebSdk.connect({ + chaperoneUrl: import.meta.env.VITE_APP_CHAPERONE_URL, + authFormCustomization: { + appName: '{{app_name}}', + } + }); + + client.on('agent-state', (agent_state: AgentState) => { + this.loading = !agent_state.isAvailable || agent_state.isAnonymous; + }); + + client.signUp({ cancellable: false }); + this.client = client; + + } else { + // We pass an unused string as the url because it will dynamically be replaced in launcher environments + this.client = await AppAgentWebsocket.connect(new URL('https://UNUSED'), '{{app_name}}'); + this.loading = false; + } + {{else}} // We pass an unused string as the url because it will dynamically be replaced in launcher environments this.client = await AppAgentWebsocket.connect(new URL('https://UNUSED'), '{{app_name}}'); - this.loading = false; + {{/if}} + } + +{{#if holo_enabled}} + async logout() { + await (this.client as WebSdk).signOut(); + await (this.client as WebSdk).signIn({ cancellable: false }); } +{{/if}} render() { if (this.loading) @@ -53,6 +93,16 @@ import './todos/todos/create-todo'; Replace this "EDIT ME!" section with <create-todo></create-todo><all-todos></all-todos>. + {{#if holo_enabled}} + ${IS_HOLO ? html` + this.logout()} + > + `: ''} + {{/if}} `; } diff --git a/templates/lit/web-app/ui/tsconfig.json.hbs b/templates/lit/web-app/ui/tsconfig.json.hbs index 4914f5b25..89a6f6466 100644 --- a/templates/lit/web-app/ui/tsconfig.json.hbs +++ b/templates/lit/web-app/ui/tsconfig.json.hbs @@ -15,6 +15,9 @@ "sourceMap": true, "inlineSources": true, "incremental": true, + {{#if holo_enabled}} + "types": ["vite/client"], + {{/if}} "skipLibCheck": true }, "include": ["src/**/*.ts", "src/**/*.d.ts"] diff --git a/templates/svelte/web-app/package.json.hbs b/templates/svelte/web-app/package.json.hbs index 62060d4b2..686c79f7c 100644 --- a/templates/svelte/web-app/package.json.hbs +++ b/templates/svelte/web-app/package.json.hbs @@ -10,15 +10,23 @@ "network": "hc s clean && npm run build:happ && UI_PORT=8888 concurrently \"npm start -w ui\" \"npm run launch:happ\" \"holochain-playground\"", "test": "npm run build:zomes && hc app pack workdir --recursive && npm t -w tests", "launch:happ": "concurrently \"hc run-local-services --bootstrap-port $BOOTSTRAP_PORT --signal-port $SIGNAL_PORT\" \"echo pass | RUST_LOG=warn hc launch --piped -n $AGENTS workdir/{{app_name}}.happ --ui-port $UI_PORT network --bootstrap http://127.0.0.1:\"$BOOTSTRAP_PORT\" webrtc ws://127.0.0.1:\"$SIGNAL_PORT\"\"", + {{#if holo_enabled}} + "start:holo": "AGENTS=2 npm run network:holo", + "network:holo": "npm run build:happ && UI_PORT=8888 concurrently \"npm run launch:holo-dev-server\" \"holochain-playground ws://localhost:4444\" \"concurrently-repeat 'VITE_APP_CHAPERONE_URL=http://localhost:24274 VITE_APP_IS_HOLO=true npm start -w ui' $AGENTS\"", + "launch:holo-dev-server": "holo-dev-server workdir/{{app_name}}.happ", + {{/if}} "package": "npm run build:happ && npm run package -w ui && hc web-app pack workdir --recursive", "build:happ": "npm run build:zomes && hc app pack workdir --recursive", - "build:zomes": "RUSTFLAGS='' CARGO_TARGET_DIR=target cargo build --release --target wasm32-unknown-unknown" + "build:zomes": "RUSTFLAGS='' CARGO_TARGET_DIR=target cargo build --release --target wasm32-unknown-unknown" }, - "devDependencies": { + "devDependencies": { "@holochain-playground/cli": "^0.1.1", "concurrently": "^6.2.1", "rimraf": "^3.0.2", "new-port-cli": "^1.0.0" + {{#if holo_enabled}} + "concurrently-repeat": "^0.0.1", + {{/if}} }, "engines": { "npm": ">=7.0.0" diff --git a/templates/svelte/web-app/ui/package.json.hbs b/templates/svelte/web-app/ui/package.json.hbs index 64d71c6c8..97c68ea03 100644 --- a/templates/svelte/web-app/ui/package.json.hbs +++ b/templates/svelte/web-app/ui/package.json.hbs @@ -9,6 +9,9 @@ }, "dependencies": { "@holochain/client": "{{holochain_client_version}}", + {{#if holo_enabled}} + "@holo-host/web-sdk": "{{web_sdk_version}}", + {{/if}} "@material/mwc-circular-progress": "^0.27.0", "@material/mwc-button": "^0.27.0", "@material/mwc-textfield": "^0.27.0", diff --git a/templates/svelte/web-app/ui/src/App.svelte.hbs b/templates/svelte/web-app/ui/src/App.svelte.hbs index 9329f866c..a2c70ca18 100644 --- a/templates/svelte/web-app/ui/src/App.svelte.hbs +++ b/templates/svelte/web-app/ui/src/App.svelte.hbs @@ -2,19 +2,54 @@ import { onMount, setContext } from 'svelte'; import type { ActionHash, AppAgentClient } from '@holochain/client'; import { AppAgentWebsocket } from '@holochain/client'; +{{#if holo_enabled}} + import WebSdk from '@holo-host/web-sdk' +{{/if}} import '@material/mwc-circular-progress'; import { clientContext } from './contexts'; let client: AppAgentClient | undefined; - let loading = true; - $: client, loading; + let loading = true; + +{{#if holo_enabled}} + const IS_HOLO = ['true', '1', 't'].includes(import.meta.env.VITE_APP_IS_HOLO?.toLowerCase()) + + const logout = async () => { + await (client as WebSdk).signOut(); + (client as WebSdk).signIn({ cancellable: false }) + } +{{/if}} onMount(async () => { + {{#if holo_enabled}} + if (IS_HOLO) { + client = await WebSdk.connect({ + chaperoneUrl: import.meta.env.VITE_APP_CHAPERONE_URL, + authFormCustomization: { + appName: '{{app_name}}', + } + }); + + (client as WebSdk).on('agent-state', agent_state => { + loading = !agent_state.isAvailable || agent_state.isAnonymous + }); + + (client as WebSdk).signUp({ cancellable: false }); + + } else { + // We pass an unused string as the url because it will dynamically be replaced in launcher environments + client = await AppAgentWebsocket.connect(new URL('https://UNUSED'), '{{app_name}}'); + + loading = false; + } + {{else}} // We pass an unused string as the url because it will dynamically be replaced in launcher environments client = await AppAgentWebsocket.connect(new URL('https://UNUSED'), '{{app_name}}'); + loading = false; + {{/if}} }); setContext(clientContext, { @@ -46,6 +81,16 @@ import CreateTodo from './todos/todos/CreateTodo.svelte'; Replace this "EDIT ME!" section with <CreateTodo></CreateTodo><AllTodos></AllTodos>. + {{#if holo_enabled}} + {#if IS_HOLO} + logout()} + /> + {/if} + {{/if}} {/if} diff --git a/templates/vanilla/example/Cargo.toml.hbs b/templates/vanilla/example/Cargo.toml.hbs new file mode 100644 index 000000000..358ee0854 --- /dev/null +++ b/templates/vanilla/example/Cargo.toml.hbs @@ -0,0 +1,20 @@ +[profile.dev] +opt-level = "z" + +[profile.release] +opt-level = "z" + +[workspace] +members = ["dnas/*/zomes/coordinator/*", "dnas/*/zomes/integrity/*"] + +[workspace.dependencies] +hdi = "=0.3.1" +hdk = "=0.2.1" +holochain_integrity_types = "=0.1.2" +serde = "=1.0.166" + +[workspace.dependencies.hello_world] +path = "dnas/hello_world/zomes/coordinator/hello_world" + +[workspace.dependencies.hello_world_integrity] +path = "dnas/hello_world/zomes/integrity/hello_world" diff --git a/templates/vanilla/example/dnas/hello_world/workdir/dna.yaml.hbs b/templates/vanilla/example/dnas/hello_world/workdir/dna.yaml.hbs new file mode 100644 index 000000000..19f9e661d --- /dev/null +++ b/templates/vanilla/example/dnas/hello_world/workdir/dna.yaml.hbs @@ -0,0 +1,19 @@ +--- +manifest_version: "1" +name: hello_world +integrity: + network_seed: ~ + properties: ~ + origin_time: 1679601731282690 + zomes: + - name: hello_world_integrity + hash: ~ + bundled: "../../../target/wasm32-unknown-unknown/release/hello_world_integrity.wasm" + dependencies: ~ +coordinator: + zomes: + - name: hello_world + hash: ~ + bundled: "../../../target/wasm32-unknown-unknown/release/hello_world.wasm" + dependencies: + - name: hello_world_integrity diff --git a/templates/vanilla/example/dnas/hello_world/zomes/coordinator/hello_world/Cargo.toml.hbs b/templates/vanilla/example/dnas/hello_world/zomes/coordinator/hello_world/Cargo.toml.hbs new file mode 100644 index 000000000..3cee1e254 --- /dev/null +++ b/templates/vanilla/example/dnas/hello_world/zomes/coordinator/hello_world/Cargo.toml.hbs @@ -0,0 +1,15 @@ +[package] +name = "hello_world" +version = "0.0.1" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] +name = "hello_world" + +[dependencies] +hdk = { workspace = true } + +serde = { workspace = true } + +hello_world_integrity = { workspace = true } diff --git a/templates/vanilla/example/dnas/hello_world/zomes/coordinator/hello_world/src/lib.rs.hbs b/templates/vanilla/example/dnas/hello_world/zomes/coordinator/hello_world/src/lib.rs.hbs new file mode 100644 index 000000000..5797591dd --- /dev/null +++ b/templates/vanilla/example/dnas/hello_world/zomes/coordinator/hello_world/src/lib.rs.hbs @@ -0,0 +1,59 @@ +use hdk::prelude::*; +use hello_world_integrity::*; + +#[hdk_extern] +pub fn hello_world(message:String) -> ExternResult { + + // commit the Hello message + let action_hash = create_entry(&EntryTypes::Hello(Hello{message}))?; + + // link it to an anchor for later retrieval + let path = Path::from("hellos"); + create_link( + path.path_entry_hash()?, + action_hash.clone(), + LinkTypes::AllHellos, + (), + )?; + Ok(action_hash) +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct HelloOutput { + pub message: String, + pub author: AgentPubKey +} + +#[hdk_extern] +pub fn get_hellos(_: ()) -> ExternResult> { + + // get all of the hellos linked to the anachor + let path = Path::from("hellos"); + let links = get_links(path.path_entry_hash()?, LinkTypes::AllHellos, None)?; + let get_input: Vec = links + .into_iter() + .map(|link| Ok(GetInput::new( + link.target.into_action_hash().ok_or(wasm_error!(WasmErrorInner::Guest(String::from("No action hash associated with link"))))?.into(), + GetOptions::default(), + ))) + .collect::>>()?; + + // load the records for all the links + let records = HDK.with(|hdk| hdk.borrow().get(get_input))?; + let records: Vec = records.into_iter().filter_map(|r| r).collect(); + + // convert the records into a usefull struct for the UI + let mut hellos = Vec::new(); + for r in records { + let maybe_hello: Option = r.entry.to_app_option().map_err(|e| wasm_error!(e))?; + if let Some(hello) = maybe_hello { + hellos.push( + HelloOutput { + message:hello.message, + author: r.action().author().clone(), + } + ) + } + } + Ok(hellos) +} diff --git a/templates/vanilla/example/dnas/hello_world/zomes/integrity/hello_world/Cargo.toml.hbs b/templates/vanilla/example/dnas/hello_world/zomes/integrity/hello_world/Cargo.toml.hbs new file mode 100644 index 000000000..ce8f18dd4 --- /dev/null +++ b/templates/vanilla/example/dnas/hello_world/zomes/integrity/hello_world/Cargo.toml.hbs @@ -0,0 +1,13 @@ +[package] +name = "hello_world_integrity" +version = "0.0.1" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] +name = "hello_world_integrity" + +[dependencies] +hdi = { workspace = true } + +serde = { workspace = true } diff --git a/templates/vanilla/example/dnas/hello_world/zomes/integrity/hello_world/src/lib.rs.hbs b/templates/vanilla/example/dnas/hello_world/zomes/integrity/hello_world/src/lib.rs.hbs new file mode 100644 index 000000000..03c00c7f2 --- /dev/null +++ b/templates/vanilla/example/dnas/hello_world/zomes/integrity/hello_world/src/lib.rs.hbs @@ -0,0 +1,49 @@ +use hdi::prelude::*; + +/// this struct defines the content of the Hello entry +#[hdk_entry_helper] +#[derive(Clone, PartialEq)] +pub struct Hello { + pub message: String, +} + +/// Definition of the Hello entry type itself using the entry-helper struct as its content +#[derive(Serialize, Deserialize)] +#[serde(tag = "type")] +#[hdk_entry_defs] +#[unit_enum(UnitEntryTypes)] +pub enum EntryTypes { + Hello(Hello), +} + +/// Definition of a link type to be used for linking from an anchor to all created entreis +#[derive(Serialize, Deserialize)] +#[hdk_link_types] +pub enum LinkTypes { + AllHellos, +} + +/// Validation you perform during the genesis process. Nobody else on the network performs it, only you. +/// There *is no* access to network calls in this callback +#[hdk_extern] +pub fn genesis_self_check(_data: GenesisSelfCheckData) -> ExternResult { + Ok(ValidateCallbackResult::Valid) +} + +/// Validation the network performs when you try to join, you can't perform this validation yourself as you are not a member yet. +/// There *is* access to network calls in this function +pub fn validate_agent_joining(_agent_pub_key: AgentPubKey, _membrane_proof: &Option) -> ExternResult { + Ok(ValidateCallbackResult::Valid) +} + +/// This is the unified validation callback for all entries and link types in this integrity zome +/// In this example app we leave validation aside, please look at the Forum example for validation samples +/// +/// You can read more about validation here: https://docs.rs/hdi/latest/hdi/index.html#data-validation +/// +/// +#[hdk_extern] +pub fn validate(_op: Op) -> ExternResult { + Ok(ValidateCallbackResult::Valid) +} + diff --git a/templates/vanilla/example/tests/hello-world.test.ts.hbs b/templates/vanilla/example/tests/hello-world.test.ts.hbs new file mode 100644 index 000000000..c1e67f066 --- /dev/null +++ b/templates/vanilla/example/tests/hello-world.test.ts.hbs @@ -0,0 +1,58 @@ +import { assert, test } from "vitest"; + +import { runScenario, pause } from '@holochain/tryorama'; + + +test('send hello and retrieve hellos', async () => { + await runScenario(async scenario => { + // Construct proper paths for your app. + // This assumes app bundle created by the `hc app pack` command. + const testAppPath = process.cwd() + '/../workdir/hello-world.happ'; + + // Set up the app to be installed + const appSource = { appBundleSource: { path: testAppPath } }; + + // Add 2 players with the test app to the Scenario. The returned players + // can be destructured. + const [alice, beto] = await scenario.addPlayersWithApps([appSource, appSource]); + + // Shortcut peer discovery through gossip and register all agents in every + // conductor of the scenario. + await scenario.shareAllAgents(); + + // Alice sends a hello + const aliceCell = alice.cells[0] + const resultAlice = await aliceCell.callZome({ + zome_name: "hello_world", + fn_name: "hello_world", + payload: "hello world!", + }); + assert.ok(resultAlice); + + // Beto sends a hello + const betoCell = beto.cells[0] + const resultBeto = await betoCell.callZome({ + zome_name: "hello_world", + fn_name: "hello_world", + payload: "hola mundo!", + }); + assert.ok(resultBeto); + + await pause(1000) // wait for DHT to settle + + // Alice gets all hellos + const hellosAlice: Array = await aliceCell.callZome({ + zome_name: "hello_world", + fn_name: "get_hellos", + payload: undefined, + }); + + // confirm that both hellos are present + assert.equal(hellosAlice.length, 2); + assert.equal(hellosAlice[0].message, "hello world!"); + assert.deepEqual(hellosAlice[0].author, aliceCell.cell_id[1]); + assert.equal(hellosAlice[1].message, "hola mundo!"); + assert.deepEqual(hellosAlice[1].author, betoCell.cell_id[1]); + + }); + }); \ No newline at end of file diff --git a/templates/vanilla/example/ui/index.html.hbs b/templates/vanilla/example/ui/index.html.hbs index db52a680f..4f96f40db 100644 --- a/templates/vanilla/example/ui/index.html.hbs +++ b/templates/vanilla/example/ui/index.html.hbs @@ -18,31 +18,86 @@ background-color: #ededed; } + Example hApp - - + + + My AgentPubKey: + Greeting sent! Click the button to see all hellos. + + Look for Hellos + + diff --git a/templates/vanilla/example/ui/package.json.hbs b/templates/vanilla/example/ui/package.json.hbs new file mode 100644 index 000000000..961a024f2 --- /dev/null +++ b/templates/vanilla/example/ui/package.json.hbs @@ -0,0 +1,27 @@ +{ + "name": "ui", + "description": "vanilla holochain web-app", + "license": "MIT", + "version": "0.0.0", + "scripts": { + "start": "vite --clearScreen false --port $UI_PORT", + "build": "vite build", + "package": "npm run build && cd dist && bestzip ../dist.zip *" + }, + "dependencies": { + "@holochain/client": "^0.12.2", + "@holo-host/identicon": "^0.1.0", + "@msgpack/msgpack": "^2.7.2" + }, + "devDependencies": { + "@open-wc/eslint-config": "^4.3.0", + "@typescript-eslint/eslint-plugin": "^4.29.2", + "@typescript-eslint/parser": "^4.29.2", + "bestzip": "^2.2.0", + "deepmerge": "^4.2.2", + "rimraf": "^3.0.2", + "tslib": "^2.3.1", + "typescript": "^4.9.3", + "vite": "^4.0.4" + } + } \ No newline at end of file diff --git a/templates/vanilla/example/workdir/happ.yaml.hbs b/templates/vanilla/example/workdir/happ.yaml.hbs new file mode 100644 index 000000000..5a9738d38 --- /dev/null +++ b/templates/vanilla/example/workdir/happ.yaml.hbs @@ -0,0 +1,18 @@ +--- +manifest_version: "1" +name: hello-world +description: ~ +roles: + - name: hello_world + provisioning: + strategy: create + deferred: false + dna: + bundled: "../dnas/hello_world/workdir/hello_world.dna" + modifiers: + network_seed: ~ + properties: ~ + origin_time: ~ + quantum_time: ~ + version: ~ + clone_limit: 0 diff --git a/templates/vanilla/example/workdir/web-happ.yaml.hbs b/templates/vanilla/example/workdir/web-happ.yaml.hbs new file mode 100644 index 000000000..0937bcc4c --- /dev/null +++ b/templates/vanilla/example/workdir/web-happ.yaml.hbs @@ -0,0 +1,7 @@ +--- +manifest_version: "1" +name: hello-world +ui: + bundled: "../ui/dist.zip" +happ_manifest: + bundled: "./hello-world.happ" diff --git a/templates/vue/web-app/package.json.hbs b/templates/vue/web-app/package.json.hbs index 62060d4b2..c16b5d3e1 100644 --- a/templates/vue/web-app/package.json.hbs +++ b/templates/vue/web-app/package.json.hbs @@ -10,6 +10,11 @@ "network": "hc s clean && npm run build:happ && UI_PORT=8888 concurrently \"npm start -w ui\" \"npm run launch:happ\" \"holochain-playground\"", "test": "npm run build:zomes && hc app pack workdir --recursive && npm t -w tests", "launch:happ": "concurrently \"hc run-local-services --bootstrap-port $BOOTSTRAP_PORT --signal-port $SIGNAL_PORT\" \"echo pass | RUST_LOG=warn hc launch --piped -n $AGENTS workdir/{{app_name}}.happ --ui-port $UI_PORT network --bootstrap http://127.0.0.1:\"$BOOTSTRAP_PORT\" webrtc ws://127.0.0.1:\"$SIGNAL_PORT\"\"", + {{#if holo_enabled}} + "start:holo": "AGENTS=2 npm run network:holo", + "network:holo": "npm run build:happ && UI_PORT=8888 concurrently \"npm run launch:holo-dev-server\" \"holochain-playground ws://localhost:4444\" \"concurrently-repeat 'VITE_APP_CHAPERONE_URL=http://localhost:24274 VITE_APP_IS_HOLO=true npm start -w ui' $AGENTS\"", + "launch:holo-dev-server": "holo-dev-server workdir/{{app_name}}.happ", + {{/if}} "package": "npm run build:happ && npm run package -w ui && hc web-app pack workdir --recursive", "build:happ": "npm run build:zomes && hc app pack workdir --recursive", "build:zomes": "RUSTFLAGS='' CARGO_TARGET_DIR=target cargo build --release --target wasm32-unknown-unknown" @@ -19,6 +24,9 @@ "concurrently": "^6.2.1", "rimraf": "^3.0.2", "new-port-cli": "^1.0.0" + {{#if holo_enabled}} + "concurrently-repeat": "^0.0.1", + {{/if}} }, "engines": { "npm": ">=7.0.0" diff --git a/templates/vue/web-app/ui/package.json.hbs b/templates/vue/web-app/ui/package.json.hbs index 766c49544..a88de3310 100644 --- a/templates/vue/web-app/ui/package.json.hbs +++ b/templates/vue/web-app/ui/package.json.hbs @@ -9,6 +9,9 @@ }, "dependencies": { "@holochain/client": "{{holochain_client_version}}", + {{#if holo_enabled}} + "@holo-host/web-sdk": "{{web_sdk_version}}", + {{/if}} "@material/mwc-circular-progress": "^0.27.0", "@material/mwc-button": "^0.27.0", "@material/mwc-textfield": "^0.27.0", diff --git a/templates/vue/web-app/ui/src/App.vue.hbs b/templates/vue/web-app/ui/src/App.vue.hbs index d689663ac..e4ad9a8bf 100644 --- a/templates/vue/web-app/ui/src/App.vue.hbs +++ b/templates/vue/web-app/ui/src/App.vue.hbs @@ -33,13 +33,27 @@ export default defineComponent({ Replace this "EDIT ME!" section with <CreateTodo></CreateTodo><AllTodos></AllTodos>. + {{#if holo_enabled}} + + {{/if}}
<create-todo></create-todo><all-todos></all-todos>
<CreateTodo></CreateTodo><AllTodos></AllTodos>