From 4da8aa6b990d89da2773f1ca79b75f7171874d97 Mon Sep 17 00:00:00 2001 From: Eric Swanson Date: Sun, 24 Nov 2024 20:51:49 -0800 Subject: [PATCH 01/15] refactor: Category -> ProjectTemplateCategory --- src/dfx-core/src/config/model/mod.rs | 1 + .../src/config/model/project_template.rs | 8 ++++ src/dfx-core/src/config/project_templates.rs | 16 ++------ src/dfx/src/commands/new.rs | 3 +- src/dfx/src/lib/project/templates.rs | 37 +++++++++---------- 5 files changed, 33 insertions(+), 32 deletions(-) create mode 100644 src/dfx-core/src/config/model/project_template.rs diff --git a/src/dfx-core/src/config/model/mod.rs b/src/dfx-core/src/config/model/mod.rs index 2014e4ae34..b29410f073 100644 --- a/src/dfx-core/src/config/model/mod.rs +++ b/src/dfx-core/src/config/model/mod.rs @@ -5,5 +5,6 @@ pub mod dfinity; pub mod extension_canister_type; pub mod local_server_descriptor; pub mod network_descriptor; +pub mod project_template; pub mod replica_config; pub mod settings_digest; diff --git a/src/dfx-core/src/config/model/project_template.rs b/src/dfx-core/src/config/model/project_template.rs new file mode 100644 index 0000000000..f35ca7e399 --- /dev/null +++ b/src/dfx-core/src/config/model/project_template.rs @@ -0,0 +1,8 @@ +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum ProjectTemplateCategory { + Backend, + Frontend, + FrontendTest, + Extra, + Support, +} diff --git a/src/dfx-core/src/config/project_templates.rs b/src/dfx-core/src/config/project_templates.rs index 28c5565131..9febc449f2 100644 --- a/src/dfx-core/src/config/project_templates.rs +++ b/src/dfx-core/src/config/project_templates.rs @@ -1,3 +1,4 @@ +use crate::config::model::project_template::ProjectTemplateCategory; use itertools::Itertools; use std::collections::BTreeMap; use std::fmt::Display; @@ -12,15 +13,6 @@ pub enum ResourceLocation { Bundled { get_archive_fn: GetArchiveFn }, } -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum Category { - Backend, - Frontend, - FrontendTest, - Extra, - Support, -} - #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct ProjectTemplateName(pub String); @@ -44,7 +36,7 @@ pub struct ProjectTemplate { /// Used to determine which CLI group (`--type`, `--backend`, `--frontend`) /// as well as for interactive selection - pub category: Category, + pub category: ProjectTemplateCategory, /// Other project templates to patch in alongside this one pub requirements: Vec, @@ -99,7 +91,7 @@ pub fn find_project_template(name: &ProjectTemplateName) -> Option Vec { +pub fn get_sorted_templates(category: ProjectTemplateCategory) -> Vec { PROJECT_TEMPLATES .get() .unwrap() @@ -114,7 +106,7 @@ pub fn get_sorted_templates(category: Category) -> Vec { .collect() } -pub fn project_template_cli_names(category: Category) -> Vec { +pub fn project_template_cli_names(category: ProjectTemplateCategory) -> Vec { PROJECT_TEMPLATES .get() .unwrap() diff --git a/src/dfx/src/commands/new.rs b/src/dfx/src/commands/new.rs index 08e67b7256..093fe23805 100644 --- a/src/dfx/src/commands/new.rs +++ b/src/dfx/src/commands/new.rs @@ -11,9 +11,10 @@ use anyhow::{anyhow, bail, ensure, Context, Error}; use clap::builder::PossibleValuesParser; use clap::Parser; use console::{style, Style}; +use dfx_core::config::model::project_template::ProjectTemplateCategory as Category; use dfx_core::config::project_templates::{ find_project_template, get_project_template, get_sorted_templates, project_template_cli_names, - Category, ProjectTemplate, ProjectTemplateName, ResourceLocation, + ProjectTemplate, ProjectTemplateName, ResourceLocation, }; use dfx_core::json::{load_json_file, save_json_file}; use dialoguer::theme::ColorfulTheme; diff --git a/src/dfx/src/lib/project/templates.rs b/src/dfx/src/lib/project/templates.rs index 364556a504..8f52cc8ec9 100644 --- a/src/dfx/src/lib/project/templates.rs +++ b/src/dfx/src/lib/project/templates.rs @@ -1,7 +1,6 @@ use crate::util::assets; -use dfx_core::config::project_templates::{ - Category, ProjectTemplate, ProjectTemplateName, ResourceLocation, -}; +use dfx_core::config::model::project_template::ProjectTemplateCategory; +use dfx_core::config::project_templates::{ProjectTemplate, ProjectTemplateName, ResourceLocation}; const NPM_INSTALL: &str = "npm install --quiet --no-progress --workspaces --if-present"; const NPM_INSTALL_SPINNER_MESSAGE: &str = "Installing node dependencies..."; @@ -16,7 +15,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_motoko_files, }, - category: Category::Backend, + category: ProjectTemplateCategory::Backend, post_create: vec![], post_create_failure_warning: None, post_create_spinner_message: None, @@ -30,7 +29,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_rust_files, }, - category: Category::Backend, + category: ProjectTemplateCategory::Backend, post_create: vec!["cargo update".to_string()], post_create_failure_warning: Some(CARGO_UPDATE_FAILURE_MESSAGE.to_string()), post_create_spinner_message: None, @@ -44,7 +43,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_azle_files, }, - category: Category::Backend, + category: ProjectTemplateCategory::Backend, post_create: vec![], post_create_failure_warning: None, post_create_spinner_message: None, @@ -58,7 +57,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_kybra_files, }, - category: Category::Backend, + category: ProjectTemplateCategory::Backend, post_create: vec![], post_create_failure_warning: None, post_create_spinner_message: None, @@ -72,7 +71,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_svelte_files, }, - category: Category::Frontend, + category: ProjectTemplateCategory::Frontend, post_create: vec![NPM_INSTALL.to_string()], post_create_failure_warning: Some(NPM_INSTALL_FAILURE_WARNING.to_string()), post_create_spinner_message: Some(NPM_INSTALL_SPINNER_MESSAGE.to_string()), @@ -86,7 +85,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_react_files, }, - category: Category::Frontend, + category: ProjectTemplateCategory::Frontend, post_create: vec![NPM_INSTALL.to_string()], post_create_failure_warning: Some(NPM_INSTALL_FAILURE_WARNING.to_string()), post_create_spinner_message: Some(NPM_INSTALL_SPINNER_MESSAGE.to_string()), @@ -100,7 +99,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_vue_files, }, - category: Category::Frontend, + category: ProjectTemplateCategory::Frontend, post_create: vec![NPM_INSTALL.to_string()], post_create_failure_warning: Some(NPM_INSTALL_FAILURE_WARNING.to_string()), post_create_spinner_message: Some(NPM_INSTALL_SPINNER_MESSAGE.to_string()), @@ -114,7 +113,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_vanillajs_files, }, - category: Category::Frontend, + category: ProjectTemplateCategory::Frontend, post_create: vec![NPM_INSTALL.to_string()], post_create_failure_warning: Some(NPM_INSTALL_FAILURE_WARNING.to_string()), post_create_spinner_message: Some(NPM_INSTALL_SPINNER_MESSAGE.to_string()), @@ -128,7 +127,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_assets_files, }, - category: Category::Frontend, + category: ProjectTemplateCategory::Frontend, post_create: vec![], post_create_failure_warning: None, post_create_spinner_message: None, @@ -142,7 +141,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_svelte_test_files, }, - category: Category::FrontendTest, + category: ProjectTemplateCategory::FrontendTest, post_create: vec![], post_create_failure_warning: None, post_create_spinner_message: None, @@ -156,7 +155,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_react_test_files, }, - category: Category::FrontendTest, + category: ProjectTemplateCategory::FrontendTest, post_create: vec![], post_create_failure_warning: None, post_create_spinner_message: None, @@ -170,7 +169,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_vue_test_files, }, - category: Category::FrontendTest, + category: ProjectTemplateCategory::FrontendTest, post_create: vec![], post_create_failure_warning: None, post_create_spinner_message: None, @@ -184,7 +183,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_vanillajs_test_files, }, - category: Category::FrontendTest, + category: ProjectTemplateCategory::FrontendTest, post_create: vec![], post_create_failure_warning: None, post_create_spinner_message: None, @@ -198,7 +197,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_internet_identity_files, }, - category: Category::Extra, + category: ProjectTemplateCategory::Extra, post_create: vec![], post_create_failure_warning: None, post_create_spinner_message: None, @@ -212,7 +211,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_bitcoin_files, }, - category: Category::Extra, + category: ProjectTemplateCategory::Extra, post_create: vec![], post_create_failure_warning: None, post_create_spinner_message: None, @@ -226,7 +225,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_js_files, }, - category: Category::Support, + category: ProjectTemplateCategory::Support, post_create: vec![], post_create_failure_warning: None, post_create_spinner_message: None, From d4e21d87a9b0707f672ff0816dc82f4bf977ede6 Mon Sep 17 00:00:00 2001 From: Eric Swanson Date: Sun, 24 Nov 2024 20:27:23 -0800 Subject: [PATCH 02/15] feat: Extensions can define project templates --- .../src/config/model/project_template.rs | 7 ++- src/dfx-core/src/config/project_templates.rs | 13 ++-- src/dfx-core/src/extension/installed.rs | 9 +++ .../src/extension/manifest/extension.rs | 60 +++++++++++++++++++ src/dfx/src/commands/new.rs | 60 +++++++++++++++++-- src/dfx/src/main.rs | 5 +- 6 files changed, 143 insertions(+), 11 deletions(-) diff --git a/src/dfx-core/src/config/model/project_template.rs b/src/dfx-core/src/config/model/project_template.rs index f35ca7e399..3dfd00a7b1 100644 --- a/src/dfx-core/src/config/model/project_template.rs +++ b/src/dfx-core/src/config/model/project_template.rs @@ -1,7 +1,12 @@ -#[derive(Debug, Clone, Eq, PartialEq)] +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "lowercase")] pub enum ProjectTemplateCategory { Backend, Frontend, + #[serde(rename = "frontend-test")] FrontendTest, Extra, Support, diff --git a/src/dfx-core/src/config/project_templates.rs b/src/dfx-core/src/config/project_templates.rs index 9febc449f2..d6194a7a5d 100644 --- a/src/dfx-core/src/config/project_templates.rs +++ b/src/dfx-core/src/config/project_templates.rs @@ -3,6 +3,7 @@ use itertools::Itertools; use std::collections::BTreeMap; use std::fmt::Display; use std::io; +use std::path::PathBuf; use std::sync::OnceLock; type GetArchiveFn = fn() -> Result>, io::Error>; @@ -11,6 +12,9 @@ type GetArchiveFn = fn() -> Result; static PROJECT_TEMPLATES: OnceLock = OnceLock::new(); -pub fn populate(builtin_templates: Vec) { - let templates = builtin_templates - .iter() - .map(|t| (t.name.clone(), t.clone())) +pub fn populate(builtin_templates: Vec, loaded_templates: Vec) { + let templates: ProjectTemplates = builtin_templates + .into_iter() + .map(|t| (t.name.clone(), t)) + .chain(loaded_templates.into_iter().map(|t| (t.name.clone(), t))) .collect(); PROJECT_TEMPLATES.set(templates).unwrap(); diff --git a/src/dfx-core/src/extension/installed.rs b/src/dfx-core/src/extension/installed.rs index 4a85ba598b..46c421ec5a 100644 --- a/src/dfx-core/src/extension/installed.rs +++ b/src/dfx-core/src/extension/installed.rs @@ -1,4 +1,6 @@ +use crate::config::project_templates::ProjectTemplate; use crate::error::extension::ConvertExtensionIntoClapCommandError; +use crate::extension::manager::ExtensionManager; use crate::extension::manifest::ExtensionManifest; use crate::extension::ExtensionName; use clap::Command; @@ -28,4 +30,11 @@ impl InstalledExtensionManifests { pub fn contains(&self, extension: &str) -> bool { self.0.contains_key(extension) } + + pub fn loaded_templates(&self, em: &ExtensionManager) -> Vec { + self.0 + .values() + .flat_map(|manifest| manifest.project_templates(em)) + .collect() + } } diff --git a/src/dfx-core/src/extension/manifest/extension.rs b/src/dfx-core/src/extension/manifest/extension.rs index 7ed0edcf5b..0e1021d68f 100644 --- a/src/dfx-core/src/extension/manifest/extension.rs +++ b/src/dfx-core/src/extension/manifest/extension.rs @@ -1,7 +1,10 @@ +use crate::config::model::project_template::ProjectTemplateCategory; +use crate::config::project_templates::{ProjectTemplate, ProjectTemplateName, ResourceLocation}; use crate::error::extension::{ ConvertExtensionSubcommandIntoClapArgError, ConvertExtensionSubcommandIntoClapCommandError, LoadExtensionManifestError, }; +use crate::extension::manager::ExtensionManager; use crate::json::structure::{VersionReqWithJsonSchema, VersionWithJsonSchema}; use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -34,6 +37,8 @@ pub struct ExtensionManifest { pub dependencies: Option>, pub canister_type: Option, + pub project_templates: Option>, + /// Components of the download url template are: /// - `{{tag}}`: the tag of the extension release, which will follow the form "-v" /// - `{{basename}}`: The basename of the release filename, which will follow the form "--", for example "nns-x86_64-unknown-linux-gnu" @@ -56,6 +61,28 @@ pub enum ExtensionDependency { Version(VersionReqWithJsonSchema), } +#[derive(Debug, Serialize, Deserialize, JsonSchema)] +pub struct ExtensionProjectTemplate { + /// The name used for display and sorting + pub display: String, + + /// Used to determine which CLI group (`--type`, `--backend`, `--frontend`) + /// as well as for interactive selection + pub category: ProjectTemplateCategory, + + /// Other project templates to patch in alongside this one + pub requirements: Vec, + + /// Run a command after adding the canister to dfx.json + pub post_create: Vec, + + /// If set, display a spinner while this command runs + pub post_create_spinner_message: Option, + + /// If the post-create command fails, display this warning but don't fail + pub post_create_failure_warning: Option, +} + impl ExtensionManifest { pub fn load( name: &str, @@ -92,6 +119,39 @@ impl ExtensionManifest { Ok(vec![]) } } + + pub fn project_templates(&self, em: &ExtensionManager) -> Vec { + let Some(project_templates) = self.project_templates.as_ref() else { + return vec![]; + }; + + let extension_dir = em.get_extension_directory(&self.name); + + project_templates + .iter() + .map(|(name, template)| { + let resource_dir = extension_dir.join("project_templates").join(name); + let resource_location = ResourceLocation::Directory { path: resource_dir }; + let sort_order = 6; + let requirements = template + .requirements + .iter() + .map(|r| ProjectTemplateName(r.clone())) + .collect(); + ProjectTemplate { + name: ProjectTemplateName(name.clone()), + display: template.display.clone(), + resource_location, + category: template.category.clone(), + requirements, + post_create: template.post_create.clone(), + post_create_spinner_message: template.post_create_spinner_message.clone(), + post_create_failure_warning: template.post_create_failure_warning.clone(), + sort_order, + } + }) + .collect() + } } #[derive(Debug, Serialize, Deserialize, JsonSchema)] diff --git a/src/dfx/src/commands/new.rs b/src/dfx/src/commands/new.rs index 093fe23805..81b3c28e88 100644 --- a/src/dfx/src/commands/new.rs +++ b/src/dfx/src/commands/new.rs @@ -29,6 +29,7 @@ use std::path::{Path, PathBuf}; use std::process::{Command, ExitStatus, Stdio}; use std::time::Duration; use tar::Archive; +use walkdir::WalkDir; // const DRY_RUN: &str = "dry_run"; // const PROJECT_NAME: &str = "project_name"; @@ -270,6 +271,52 @@ fn write_files_from_entries( Ok(()) } +fn write_files_from_directory( + log: &Logger, + dir: &Path, + root: &Path, + dry_run: bool, + variables: &BTreeMap, +) -> DfxResult { + for entry in WalkDir::new(dir).into_iter().filter_map(Result::ok) { + let path = entry.path(); + + if path.is_dir() { + continue; + } + + // Read file contents into a Vec + let file_content = dfx_core::fs::read(path)?; + + // Process the file content (replace variables) + let processed_content = match String::from_utf8(file_content) { + Err(err) => err.into_bytes(), + Ok(s) => replace_variables(s, variables).into_bytes(), + }; + + // Perform path replacements + let relative_path = path + .strip_prefix(dir)? + .to_str() + .ok_or_else(|| anyhow!("Non-unicode path encountered: {}", path.display()))?; + let relative_path = replace_variables(relative_path.to_string(), variables); + + // Build the final target path + let final_path = root.join(&relative_path); + + // Process files based on their extension + if final_path.extension() == Some("json-patch".as_ref()) { + json_patch_file(log, &final_path, &processed_content, dry_run)?; + } else if final_path.extension() == Some("patch".as_ref()) { + patch_file(log, &final_path, &processed_content, dry_run)?; + } else { + create_file(log, &final_path, &processed_content, dry_run)?; + } + } + + Ok(()) +} + #[context("Failed to scaffold frontend code.")] fn scaffold_frontend_code( env: &dyn Environment, @@ -706,10 +753,15 @@ fn write_project_template_resources( dry_run: bool, variables: &BTreeMap, ) -> DfxResult { - let mut resources = match template.resource_location { - ResourceLocation::Bundled { get_archive_fn } => get_archive_fn()?, - }; - write_files_from_entries(logger, &mut resources, project_name, dry_run, variables) + match &template.resource_location { + ResourceLocation::Bundled { get_archive_fn } => { + let mut resources = get_archive_fn()?; + write_files_from_entries(logger, &mut resources, project_name, dry_run, variables) + } + ResourceLocation::Directory { path } => { + write_files_from_directory(logger, path, project_name, dry_run, variables) + } + } } fn get_opts_interactively(opts: NewOpts) -> DfxResult { diff --git a/src/dfx/src/main.rs b/src/dfx/src/main.rs index ef86de36d3..c33998a703 100644 --- a/src/dfx/src/main.rs +++ b/src/dfx/src/main.rs @@ -137,7 +137,8 @@ fn get_args_altered_for_extension_run( fn inner_main() -> DfxResult { let em = ExtensionManager::new(dfx_version())?; let installed_extension_manifests = em.load_installed_extension_manifests()?; - project_templates::populate(builtin_templates()); + let loaded_templates = installed_extension_manifests.loaded_templates(&em); + project_templates::populate(builtin_templates(), loaded_templates); let args = get_args_altered_for_extension_run(&installed_extension_manifests)?; @@ -201,7 +202,7 @@ mod tests { #[test] fn validate_cli() { - project_templates::populate(builtin_templates()); + project_templates::populate(builtin_templates(), vec![]); CliOpts::command().debug_assert(); } From 09b3c392b35373fb516232aa749583f795f9c16c Mon Sep 17 00:00:00 2001 From: Eric Swanson Date: Sun, 24 Nov 2024 21:24:38 -0800 Subject: [PATCH 03/15] checkpoint --- e2e/tests-dfx/extension.bash | 69 +++++++++++++++++++ e2e/utils/_.bash | 8 ++- .../src/extension/manifest/extension.rs | 6 +- 3 files changed, 78 insertions(+), 5 deletions(-) diff --git a/e2e/tests-dfx/extension.bash b/e2e/tests-dfx/extension.bash index 7aa52b2122..8f0a3a0723 100644 --- a/e2e/tests-dfx/extension.bash +++ b/e2e/tests-dfx/extension.bash @@ -14,6 +14,75 @@ teardown() { standard_teardown } +@test "extension-defined project template" { + start_webserver --directory www + EXTENSION_URL="http://localhost:$E2E_WEB_SERVER_PORT/arbitrary/extension.json" + mkdir -p www/arbitrary/downloads www/arbitrary/project_templates/a-template + + cat > www/arbitrary/extension.json < www/arbitrary/dependencies.json <=0.8.0" + } + } +} +EOF + + cp -R "${BATS_TEST_DIRNAME}/../../src/dfx/assets/project_templates/rust" www/arbitrary/project_templates/rust-by-extension + + ARCHIVE_BASENAME="an-extension-v0.1.0" + + mkdir "$ARCHIVE_BASENAME" + cp www/arbitrary/extension.json "$ARCHIVE_BASENAME" + cp -R www/arbitrary/project_templates "$ARCHIVE_BASENAME" + tar -czf "$ARCHIVE_BASENAME".tar.gz "$ARCHIVE_BASENAME" + rm -rf "$ARCHIVE_BASENAME" + + mv "$ARCHIVE_BASENAME".tar.gz www/arbitrary/downloads/ + + assert_command dfx extension install "$EXTENSION_URL" + + find "$(dfx cache show)"/extensions -print + + setup_rust + + dfx new rbe --type rust-by-extension --no-frontend + dfx new rbc --type rust --no-frontend + echo "RBE" + cat rbe/dfx.json + find rbe -type f + echo "RBC" + cat rbc/dfx.json + find rbc -type f + cd rbe || exit + cargo update + dfx_start + assert_command dfx deploy +} + @test "run an extension command with a canister type defined by another extension" { install_shared_asset subnet_type/shared_network_settings/system dfx_start_for_nns_install diff --git a/e2e/utils/_.bash b/e2e/utils/_.bash index 5ec7b8466e..889253e68a 100644 --- a/e2e/utils/_.bash +++ b/e2e/utils/_.bash @@ -82,10 +82,14 @@ dfx_new() { echo PWD: "$(pwd)" >&2 } -dfx_new_rust() { - local project_name=${1:-e2e_project} +setup_rust() { rustup default stable rustup target add wasm32-unknown-unknown +} + +dfx_new_rust() { + local project_name=${1:-e2e_project} + setup_rust dfx new "${project_name}" --type=rust --no-frontend test -d "${project_name}" test -f "${project_name}/dfx.json" diff --git a/src/dfx-core/src/extension/manifest/extension.rs b/src/dfx-core/src/extension/manifest/extension.rs index 0e1021d68f..e2a629f0c4 100644 --- a/src/dfx-core/src/extension/manifest/extension.rs +++ b/src/dfx-core/src/extension/manifest/extension.rs @@ -5,7 +5,7 @@ use crate::error::extension::{ LoadExtensionManifestError, }; use crate::extension::manager::ExtensionManager; -use crate::json::structure::{VersionReqWithJsonSchema, VersionWithJsonSchema}; +use crate::json::structure::{SerdeVec, VersionReqWithJsonSchema, VersionWithJsonSchema}; use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::Value; @@ -74,7 +74,7 @@ pub struct ExtensionProjectTemplate { pub requirements: Vec, /// Run a command after adding the canister to dfx.json - pub post_create: Vec, + pub post_create: SerdeVec, /// If set, display a spinner while this command runs pub post_create_spinner_message: Option, @@ -144,7 +144,7 @@ impl ExtensionManifest { resource_location, category: template.category.clone(), requirements, - post_create: template.post_create.clone(), + post_create: template.post_create.clone().into_vec(), post_create_spinner_message: template.post_create_spinner_message.clone(), post_create_failure_warning: template.post_create_failure_warning.clone(), sort_order, From e256db2910178fa9b46791a146c3b456fbb18642 Mon Sep 17 00:00:00 2001 From: Eric Swanson Date: Sun, 24 Nov 2024 21:36:24 -0800 Subject: [PATCH 04/15] checkpoint --- e2e/tests-dfx/extension.bash | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/e2e/tests-dfx/extension.bash b/e2e/tests-dfx/extension.bash index 8f0a3a0723..ce6938ed9e 100644 --- a/e2e/tests-dfx/extension.bash +++ b/e2e/tests-dfx/extension.bash @@ -70,17 +70,19 @@ EOF setup_rust dfx new rbe --type rust-by-extension --no-frontend - dfx new rbc --type rust --no-frontend + #dfx new rbc --type rust --no-frontend echo "RBE" cat rbe/dfx.json find rbe -type f - echo "RBC" - cat rbc/dfx.json - find rbc -type f + #echo "RBC" + #cat rbc/dfx.json + #find rbc -type f cd rbe || exit - cargo update + #cargo update dfx_start assert_command dfx deploy + assert_command dfx canister call rbe_backend greet '("Rust By Extension")' + assert_contains "Hello, Rust By Extension!" } @test "run an extension command with a canister type defined by another extension" { From 7cbd5c2952c18bf35d5729eba8bee79127591765 Mon Sep 17 00:00:00 2001 From: Eric Swanson Date: Sun, 24 Nov 2024 22:02:08 -0800 Subject: [PATCH 05/15] checkpoint --- docs/extension-manifest-schema.json | 84 +++++++++++++++++++ e2e/tests-dfx/extension.bash | 74 ++++++++++++++++ src/dfx-core/src/extension/installed.rs | 8 +- .../src/extension/manifest/extension.rs | 24 +++++- src/dfx/src/main.rs | 5 +- 5 files changed, 189 insertions(+), 6 deletions(-) diff --git a/docs/extension-manifest-schema.json b/docs/extension-manifest-schema.json index beaff0a894..544823b5cb 100644 --- a/docs/extension-manifest-schema.json +++ b/docs/extension-manifest-schema.json @@ -70,6 +70,15 @@ "name": { "type": "string" }, + "project_templates": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "$ref": "#/definitions/ExtensionProjectTemplate" + } + }, "subcommands": { "anyOf": [ { @@ -155,6 +164,58 @@ } ] }, + "ExtensionProjectTemplate": { + "type": "object", + "required": [ + "category", + "display", + "post_create", + "requirements" + ], + "properties": { + "category": { + "description": "Used to determine which CLI group (`--type`, `--backend`, `--frontend`) as well as for interactive selection", + "allOf": [ + { + "$ref": "#/definitions/ProjectTemplateCategory" + } + ] + }, + "display": { + "description": "The name used for display and sorting", + "type": "string" + }, + "post_create": { + "description": "Run a command after adding the canister to dfx.json", + "allOf": [ + { + "$ref": "#/definitions/SerdeVec_for_String" + } + ] + }, + "post_create_failure_warning": { + "description": "If the post-create command fails, display this warning but don't fail", + "type": [ + "string", + "null" + ] + }, + "post_create_spinner_message": { + "description": "If set, display a spinner while this command runs", + "type": [ + "string", + "null" + ] + }, + "requirements": { + "description": "Other project templates to patch in alongside this one", + "type": "array", + "items": { + "type": "string" + } + } + } + }, "ExtensionSubcommandArgOpts": { "type": "object", "properties": { @@ -231,6 +292,16 @@ "$ref": "#/definitions/ExtensionSubcommandOpts" } }, + "ProjectTemplateCategory": { + "type": "string", + "enum": [ + "backend", + "frontend", + "frontend-test", + "extra", + "support" + ] + }, "Range_of_uint": { "type": "object", "required": [ @@ -249,6 +320,19 @@ "minimum": 0.0 } } + }, + "SerdeVec_for_String": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] } } } \ No newline at end of file diff --git a/e2e/tests-dfx/extension.bash b/e2e/tests-dfx/extension.bash index ce6938ed9e..5a5f597e1f 100644 --- a/e2e/tests-dfx/extension.bash +++ b/e2e/tests-dfx/extension.bash @@ -85,6 +85,80 @@ EOF assert_contains "Hello, Rust By Extension!" } +@test "extension-defined project template replaces built-in type" { + start_webserver --directory www + EXTENSION_URL="http://localhost:$E2E_WEB_SERVER_PORT/arbitrary/extension.json" + mkdir -p www/arbitrary/downloads www/arbitrary/project_templates/a-template + + cat > www/arbitrary/extension.json < www/arbitrary/dependencies.json <=0.8.0" + } + } +} +EOF + + cp -R "${BATS_TEST_DIRNAME}/../../src/dfx/assets/project_templates/rust" www/arbitrary/project_templates/rust + echo "just-proves-it-used-the-project-template" > www/arbitrary/project_templates/rust/proof.txt + + ARCHIVE_BASENAME="an-extension-v0.1.0" + + mkdir "$ARCHIVE_BASENAME" + cp www/arbitrary/extension.json "$ARCHIVE_BASENAME" + cp -R www/arbitrary/project_templates "$ARCHIVE_BASENAME" + tar -czf "$ARCHIVE_BASENAME".tar.gz "$ARCHIVE_BASENAME" + rm -rf "$ARCHIVE_BASENAME" + + mv "$ARCHIVE_BASENAME".tar.gz www/arbitrary/downloads/ + + assert_command dfx extension install "$EXTENSION_URL" + + find "$(dfx cache show)"/extensions -print + + setup_rust + + dfx new rbe --type rust --no-frontend + assert_command cat rbe/proof.txt + assert_eq "just-proves-it-used-the-project-template" + + #dfx new rbc --type rust --no-frontend + echo "RBE" + cat rbe/dfx.json + find rbe -type f + #echo "RBC" + #cat rbc/dfx.json + #find rbc -type f + cd rbe || exit + #cargo update + dfx_start + assert_command dfx deploy + assert_command dfx canister call rbe_backend greet '("Rust By Extension")' + assert_contains "Hello, Rust By Extension!" +} + @test "run an extension command with a canister type defined by another extension" { install_shared_asset subnet_type/shared_network_settings/system dfx_start_for_nns_install diff --git a/src/dfx-core/src/extension/installed.rs b/src/dfx-core/src/extension/installed.rs index 46c421ec5a..c701b3949c 100644 --- a/src/dfx-core/src/extension/installed.rs +++ b/src/dfx-core/src/extension/installed.rs @@ -31,10 +31,14 @@ impl InstalledExtensionManifests { self.0.contains_key(extension) } - pub fn loaded_templates(&self, em: &ExtensionManager) -> Vec { + pub fn loaded_templates( + &self, + em: &ExtensionManager, + builtin_templates: &[ProjectTemplate], + ) -> Vec { self.0 .values() - .flat_map(|manifest| manifest.project_templates(em)) + .flat_map(|manifest| manifest.project_templates(em, builtin_templates)) .collect() } } diff --git a/src/dfx-core/src/extension/manifest/extension.rs b/src/dfx-core/src/extension/manifest/extension.rs index e2a629f0c4..b72b6619de 100644 --- a/src/dfx-core/src/extension/manifest/extension.rs +++ b/src/dfx-core/src/extension/manifest/extension.rs @@ -120,19 +120,39 @@ impl ExtensionManifest { } } - pub fn project_templates(&self, em: &ExtensionManager) -> Vec { + pub fn project_templates( + &self, + em: &ExtensionManager, + builtin_templates: &[ProjectTemplate], + ) -> Vec { let Some(project_templates) = self.project_templates.as_ref() else { return vec![]; }; let extension_dir = em.get_extension_directory(&self.name); + // the default sort order is after everything built-in + let default_sort_order = builtin_templates + .iter() + .map(|t| t.sort_order) + .max() + .unwrap_or(0) + + 1; + project_templates .iter() .map(|(name, template)| { let resource_dir = extension_dir.join("project_templates").join(name); let resource_location = ResourceLocation::Directory { path: resource_dir }; - let sort_order = 6; + + // keep the sort order as a built-in template of the same name, + // otherwise put it after everything else + let sort_order = builtin_templates + .iter() + .find(|t| t.name == ProjectTemplateName(name.clone())) + .map(|t| t.sort_order) + .unwrap_or(default_sort_order); + let requirements = template .requirements .iter() diff --git a/src/dfx/src/main.rs b/src/dfx/src/main.rs index c33998a703..79a19d3542 100644 --- a/src/dfx/src/main.rs +++ b/src/dfx/src/main.rs @@ -137,8 +137,9 @@ fn get_args_altered_for_extension_run( fn inner_main() -> DfxResult { let em = ExtensionManager::new(dfx_version())?; let installed_extension_manifests = em.load_installed_extension_manifests()?; - let loaded_templates = installed_extension_manifests.loaded_templates(&em); - project_templates::populate(builtin_templates(), loaded_templates); + let builtin_templates = builtin_templates(); + let loaded_templates = installed_extension_manifests.loaded_templates(&em, &builtin_templates); + project_templates::populate(builtin_templates, loaded_templates); let args = get_args_altered_for_extension_run(&installed_extension_manifests)?; From 94635bce073e1a83bac108c88283574791be386f Mon Sep 17 00:00:00 2001 From: Eric Swanson Date: Sun, 24 Nov 2024 22:08:07 -0800 Subject: [PATCH 06/15] docs --- CHANGELOG.md | 5 +++++ docs/concepts/extension-defined-project-templates.md | 10 ++++++++++ docs/concepts/index.md | 4 +++- 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 docs/concepts/extension-defined-project-templates.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ecc8f39df..2846112332 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ Users can surpress this error by setting `export DFX_WARNING=-mainnet_plaintext_ The warning won't display when executing commands like `dfx deploy --playground`. +### feat: extensions can define project templates + +An extension can define 1 or more project templates that can be used with `dfx new`. +These can replace the built-in project templates or be additional templates. + # 0.24.3 ### feat: Bitcoin support in PocketIC diff --git a/docs/concepts/extension-defined-project-templates.md b/docs/concepts/extension-defined-project-templates.md new file mode 100644 index 0000000000..0b699d45c0 --- /dev/null +++ b/docs/concepts/extension-defined-project-templates.md @@ -0,0 +1,10 @@ +# Extension-Defined Canister Types + +## Overview + +An extension can define one or more project templates for `dfx new` to use. + +# Specification + +The `project_templates` field in an extension's `extension.json` defines the +characteristics of the canister type. It has the following fields: diff --git a/docs/concepts/index.md b/docs/concepts/index.md index 4f45495c5b..535cc88d01 100644 --- a/docs/concepts/index.md +++ b/docs/concepts/index.md @@ -1,4 +1,6 @@ # DFX Concepts - [Asset Canister Interface](../design/asset-canister-interface.md) -- [Canister metadata](./canister-metadata.md) +- [Canister metadata]( +- [Extension-Defined Canister Types](extension-defined-canister-types.md) +- [Extension-Defined Canister Types](extension-defined-project-templates.md) From 4a2b6d5b1274f7c0690f78d41d16bfc2dfa57859 Mon Sep 17 00:00:00 2001 From: Eric Swanson Date: Sun, 24 Nov 2024 22:11:17 -0800 Subject: [PATCH 07/15] docs --- docs/concepts/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/concepts/index.md b/docs/concepts/index.md index 535cc88d01..7082b99b78 100644 --- a/docs/concepts/index.md +++ b/docs/concepts/index.md @@ -1,6 +1,6 @@ # DFX Concepts - [Asset Canister Interface](../design/asset-canister-interface.md) -- [Canister metadata]( +- [Canister metadata](canister-metadata.md) - [Extension-Defined Canister Types](extension-defined-canister-types.md) -- [Extension-Defined Canister Types](extension-defined-project-templates.md) +- [Extension-Defined Project Templates](extension-defined-project-templates.md) From 54b50f3e451f71809b8806231dfd70acf79f47dc Mon Sep 17 00:00:00 2001 From: Eric Swanson Date: Sun, 24 Nov 2024 22:21:07 -0800 Subject: [PATCH 08/15] shellcheck --- e2e/tests-dfx/extension.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/tests-dfx/extension.bash b/e2e/tests-dfx/extension.bash index 5a5f597e1f..eeeec3537f 100644 --- a/e2e/tests-dfx/extension.bash +++ b/e2e/tests-dfx/extension.bash @@ -34,7 +34,7 @@ teardown() { "display": "rust by extension", "requirements": [], "post_create": "cargo update", - "port_create_failure_warning": "You will need to run it yourself (or a similar command like `cargo vendor`), because `dfx build` will use the --locked flag with Cargo." + "port_create_failure_warning": "You will need to run it yourself (or a similar command like 'cargo vendor'), because 'dfx build' will use the --locked flag with Cargo." } }, "download_url_template": "http://localhost:$E2E_WEB_SERVER_PORT/arbitrary/downloads/{{tag}}.{{archive-format}}" From ef1ee64d4ddaf333dfa027bd67acfdb4810fe308 Mon Sep 17 00:00:00 2001 From: Eric Swanson Date: Sun, 24 Nov 2024 22:22:00 -0800 Subject: [PATCH 09/15] docs --- docs/concepts/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/concepts/index.md b/docs/concepts/index.md index 7082b99b78..cf91724f81 100644 --- a/docs/concepts/index.md +++ b/docs/concepts/index.md @@ -1,6 +1,6 @@ # DFX Concepts - [Asset Canister Interface](../design/asset-canister-interface.md) -- [Canister metadata](canister-metadata.md) -- [Extension-Defined Canister Types](extension-defined-canister-types.md) -- [Extension-Defined Project Templates](extension-defined-project-templates.md) +- [Canister metadata](./canister-metadata.md) +- [Extension-Defined Canister Types](./extension-defined-canister-types.md) +- [Extension-Defined Project Templates](./extension-defined-project-templates.md) From 248abacb4ea0a6762c0b057cd7fe9f9119db0517 Mon Sep 17 00:00:00 2001 From: Eric Swanson Date: Mon, 25 Nov 2024 09:04:47 -0800 Subject: [PATCH 10/15] docs --- .../extension-defined-project-templates.md | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/docs/concepts/extension-defined-project-templates.md b/docs/concepts/extension-defined-project-templates.md index 0b699d45c0..ca2b18ea32 100644 --- a/docs/concepts/extension-defined-project-templates.md +++ b/docs/concepts/extension-defined-project-templates.md @@ -4,7 +4,60 @@ An extension can define one or more project templates for `dfx new` to use. +A project template is a set of files and directories that `dfx new` copies or patches into a new project. + +For examples of project templates, see the [project_templates] directory in the SDK repository. + # Specification The `project_templates` field in an extension's `extension.json` defines the characteristics of the canister type. It has the following fields: + +| Field | Type | Description | +|------------------------------|---------------------------|-------------------------------------------------------------------| +| `display` | String | Display name of the project template | +| `category` | Array | Category for inclusion in `--backend` and `--frontend` CLI options | +| `requirements` | Array of String | Required project templates | +| `post_create` | String or Array of String | Command(s) to run after adding the canister to the project | +| `post_create_spinner_message` | String | Message to display while running the post_create command | +| `post_create_failure_warning` | String | Warning to display if the post_create command fails | + +## The `display` field + +The `display` field is a string that describes the project template. +It is displayed to the user when they run `dfx new`. + +## The `category` field + +The `category` field is an array of strings that categorize the project template. +`dfx new` uses this field to determine whether to include this project template +as an option for the `--backend` and `-frontend` flags, as well as in interactive setup. + +Valid values for the field: +- `frontend` +- `backend` +- `extra` +- `frontend-test` +- `support` + +## The `requirements` field + +The `requirements` field lists any project templates that `dfx new` must apply before this project template. +For example, many of the frontend templates depend on the `dfx_js_base` template, which provides +package.json and tsconfig.json. + +## The `post_create` field + +The `post_create` field specifies a command to run after adding the project template files to the project. +For example, the rust project template runs `cargo update` after adding the files. + +## The `post_create_spinner_message` field + +The `post_create_spinner_message` field is a string that `dfx new` displays while running the `post_create` command. + +## The `post_create_failure_warning` field + +The `post_create_failure_warning` field is a string that `dfx new` displays as a warning if the `post_create` command fails, +instead of an error. `dfx new` will continue creating the project in this case. + +[project_templates]: https://github.com/dfinity/sdk/tree/master/src/dfx/assets/project_templates From 903df629266cbb574d3d4ceed9616b19e488be9e Mon Sep 17 00:00:00 2001 From: Eric Swanson Date: Mon, 25 Nov 2024 09:15:22 -0800 Subject: [PATCH 11/15] docs --- CHANGELOG.md | 4 +-- .../extension-defined-project-templates.md | 27 ++++++++++--------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2846112332..a6957e0735 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,8 +16,8 @@ The warning won't display when executing commands like `dfx deploy --playground` ### feat: extensions can define project templates -An extension can define 1 or more project templates that can be used with `dfx new`. -These can replace the built-in project templates or be additional templates. +An extension can define one or more project templates for `dfx new` to use. +These can be new templates or replace the built-in project templates. # 0.24.3 diff --git a/docs/concepts/extension-defined-project-templates.md b/docs/concepts/extension-defined-project-templates.md index ca2b18ea32..a5a17199b0 100644 --- a/docs/concepts/extension-defined-project-templates.md +++ b/docs/concepts/extension-defined-project-templates.md @@ -4,28 +4,32 @@ An extension can define one or more project templates for `dfx new` to use. -A project template is a set of files and directories that `dfx new` copies or patches into a new project. +A project template is a set of files that `dfx new` copies or patches into a new project. -For examples of project templates, see the [project_templates] directory in the SDK repository. +For examples of project template files, see the [project_templates] directory in the SDK repository. # Specification -The `project_templates` field in an extension's `extension.json` defines the -characteristics of the canister type. It has the following fields: +The `project_templates` field in an extension's `extension.json` defines the project templates +included in the extension. It is an object field mapping `project template name -> project template properties`. +These are the properties of a project template: | Field | Type | Description | |------------------------------|---------------------------|-------------------------------------------------------------------| | `display` | String | Display name of the project template | -| `category` | Array | Category for inclusion in `--backend` and `--frontend` CLI options | +| `category` | String | Category for inclusion in `--backend` and `--frontend` CLI options | | `requirements` | Array of String | Required project templates | | `post_create` | String or Array of String | Command(s) to run after adding the canister to the project | | `post_create_spinner_message` | String | Message to display while running the post_create command | | `post_create_failure_warning` | String | Warning to display if the post_create command fails | +Within the files distributed with the extension, the project template files are +located in the `project_templates/{project template name}` directory. + ## The `display` field The `display` field is a string that describes the project template. -It is displayed to the user when they run `dfx new`. +`dfx new` will use this value for interactive selection of project templates. ## The `category` field @@ -43,21 +47,20 @@ Valid values for the field: ## The `requirements` field The `requirements` field lists any project templates that `dfx new` must apply before this project template. -For example, many of the frontend templates depend on the `dfx_js_base` template, which provides -package.json and tsconfig.json. +For example, many of the frontend templates depend on the `dfx_js_base` template, which adds +package.json and tsconfig.json to the project. ## The `post_create` field -The `post_create` field specifies a command to run after adding the project template files to the project. +The `post_create` field specifies a command or commands to run after adding the project template files to the project. For example, the rust project template runs `cargo update` after adding the files. ## The `post_create_spinner_message` field -The `post_create_spinner_message` field is a string that `dfx new` displays while running the `post_create` command. +If this field is set, `dfx new` will display a spinner with this message while running the `post_create` command. ## The `post_create_failure_warning` field -The `post_create_failure_warning` field is a string that `dfx new` displays as a warning if the `post_create` command fails, -instead of an error. `dfx new` will continue creating the project in this case. +If this field is present and the `post_create` command fails, `dfx new` will display this warning but won't stop creating the project. [project_templates]: https://github.com/dfinity/sdk/tree/master/src/dfx/assets/project_templates From 4e15e6345bf4d1ef0c4a698946fcf861f9b0f823 Mon Sep 17 00:00:00 2001 From: Eric Swanson Date: Mon, 25 Nov 2024 09:17:59 -0800 Subject: [PATCH 12/15] cleanup --- e2e/tests-dfx/extension.bash | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/e2e/tests-dfx/extension.bash b/e2e/tests-dfx/extension.bash index eeeec3537f..dd97978932 100644 --- a/e2e/tests-dfx/extension.bash +++ b/e2e/tests-dfx/extension.bash @@ -70,15 +70,8 @@ EOF setup_rust dfx new rbe --type rust-by-extension --no-frontend - #dfx new rbc --type rust --no-frontend - echo "RBE" - cat rbe/dfx.json - find rbe -type f - #echo "RBC" - #cat rbc/dfx.json - #find rbc -type f cd rbe || exit - #cargo update + dfx_start assert_command dfx deploy assert_command dfx canister call rbe_backend greet '("Rust By Extension")' @@ -144,15 +137,8 @@ EOF assert_command cat rbe/proof.txt assert_eq "just-proves-it-used-the-project-template" - #dfx new rbc --type rust --no-frontend - echo "RBE" - cat rbe/dfx.json - find rbe -type f - #echo "RBC" - #cat rbc/dfx.json - #find rbc -type f cd rbe || exit - #cargo update + dfx_start assert_command dfx deploy assert_command dfx canister call rbe_backend greet '("Rust By Extension")' From b30d79aecf908766bc7e2b5ea1473beca1c55ce9 Mon Sep 17 00:00:00 2001 From: Eric Swanson Date: Mon, 25 Nov 2024 09:20:06 -0800 Subject: [PATCH 13/15] cleanup --- e2e/tests-dfx/extension.bash | 4 ---- 1 file changed, 4 deletions(-) diff --git a/e2e/tests-dfx/extension.bash b/e2e/tests-dfx/extension.bash index dd97978932..b4059ffab3 100644 --- a/e2e/tests-dfx/extension.bash +++ b/e2e/tests-dfx/extension.bash @@ -65,8 +65,6 @@ EOF assert_command dfx extension install "$EXTENSION_URL" - find "$(dfx cache show)"/extensions -print - setup_rust dfx new rbe --type rust-by-extension --no-frontend @@ -129,8 +127,6 @@ EOF assert_command dfx extension install "$EXTENSION_URL" - find "$(dfx cache show)"/extensions -print - setup_rust dfx new rbe --type rust --no-frontend From 361a221690c6b0a743a24ea4ab6cc1877869e27d Mon Sep 17 00:00:00 2001 From: Eric Swanson Date: Mon, 25 Nov 2024 09:22:58 -0800 Subject: [PATCH 14/15] . --- docs/concepts/extension-defined-project-templates.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/concepts/extension-defined-project-templates.md b/docs/concepts/extension-defined-project-templates.md index a5a17199b0..0bfa0f7e13 100644 --- a/docs/concepts/extension-defined-project-templates.md +++ b/docs/concepts/extension-defined-project-templates.md @@ -1,4 +1,4 @@ -# Extension-Defined Canister Types +# Extension-Defined Project Templates ## Overview From 833de38dd35fa229225216bd13538872961a400a Mon Sep 17 00:00:00 2001 From: Eric Swanson Date: Mon, 25 Nov 2024 09:23:36 -0800 Subject: [PATCH 15/15] . --- .../extension-defined-project-templates.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/concepts/extension-defined-project-templates.md b/docs/concepts/extension-defined-project-templates.md index 0bfa0f7e13..b66789221a 100644 --- a/docs/concepts/extension-defined-project-templates.md +++ b/docs/concepts/extension-defined-project-templates.md @@ -14,14 +14,14 @@ The `project_templates` field in an extension's `extension.json` defines the pro included in the extension. It is an object field mapping `project template name -> project template properties`. These are the properties of a project template: -| Field | Type | Description | -|------------------------------|---------------------------|-------------------------------------------------------------------| -| `display` | String | Display name of the project template | -| `category` | String | Category for inclusion in `--backend` and `--frontend` CLI options | -| `requirements` | Array of String | Required project templates | -| `post_create` | String or Array of String | Command(s) to run after adding the canister to the project | -| `post_create_spinner_message` | String | Message to display while running the post_create command | -| `post_create_failure_warning` | String | Warning to display if the post_create command fails | +| Field | Type | Description | +|------------------------------|---------------------------|------------------------------------------------------------------------------------------------------| +| `display` | String | Display name of the project template | +| `category` | String | Category for inclusion in `--backend` and `--frontend` CLI options, as well as interactive selection | +| `requirements` | Array of String | Required project templates | +| `post_create` | String or Array of String | Command(s) to run after adding the canister to the project | +| `post_create_spinner_message` | String | Message to display while running the post_create command | +| `post_create_failure_warning` | String | Warning to display if the post_create command fails | Within the files distributed with the extension, the project template files are located in the `project_templates/{project template name}` directory.