From c12d57c9bed67bd7a26bfbae378e7902e2104f99 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Sun, 11 Sep 2022 22:43:33 -0700 Subject: [PATCH] new: Add custom template filters. (#309) * Add filters. * Add tests. * Add logging. * Fill out docs. * Get prism working. --- Cargo.lock | 18 +++ crates/cli/src/commands/generate.rs | 115 ++++++++++++++---- crates/cli/tests/generate_test.rs | 16 +++ ...e_test__interpolates_destination_path.snap | 3 +- ...existing_files_when_interpolated_path.snap | 1 + ...enerate_test__supports_custom_filters.snap | 12 ++ crates/generator/Cargo.toml | 3 + crates/generator/src/filters.rs | 34 ++++++ crates/generator/src/generator.rs | 33 ++++- crates/generator/src/lib.rs | 1 + crates/generator/src/template.rs | 47 ++++++- .../generator/templates/vars/filters.txt | 6 + website/docs/commands/generate.mdx | 8 +- website/docs/guides/codegen.mdx | 89 +++++++++++++- website/docs/guides/profile.mdx | 3 + website/docs/guides/root-project.mdx | 4 + website/docusaurus.config.js | 2 +- website/src/theme/prism-include-languages.js | 26 ++++ 18 files changed, 387 insertions(+), 34 deletions(-) create mode 100644 crates/cli/tests/snapshots/generate_test__supports_custom_filters.snap create mode 100644 crates/generator/src/filters.rs create mode 100644 tests/fixtures/generator/templates/vars/filters.txt create mode 100644 website/src/theme/prism-include-languages.js diff --git a/Cargo.lock b/Cargo.lock index 6e48320574c..e3edb328b98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -469,6 +469,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -1509,12 +1518,15 @@ name = "moon_generator" version = "0.1.0" dependencies = [ "assert_fs", + "convert_case", "futures", "lazy_static", "moon_config", "moon_constants", "moon_error", + "moon_logger", "moon_utils", + "serde_json", "tera", "thiserror", "tokio", @@ -3019,6 +3031,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" + [[package]] name = "unicode-width" version = "0.1.9" diff --git a/crates/cli/src/commands/generate.rs b/crates/cli/src/commands/generate.rs index f7ef8c8f271..ed865423d0b 100644 --- a/crates/cli/src/commands/generate.rs +++ b/crates/cli/src/commands/generate.rs @@ -4,11 +4,12 @@ use dialoguer::{theme::Theme, Confirm, Input, MultiSelect, Select}; use moon_config::TemplateVariable; use moon_error::MoonError; use moon_generator::{FileState, Generator, GeneratorError, Template, TemplateContext}; -use moon_logger::{color, warn}; +use moon_logger::{color, debug, map_list, trace, warn}; use moon_terminal::create_theme; use moon_utils::path; use std::collections::HashMap; use std::env; +use std::fmt::Display; use std::path::PathBuf; const LOG_TARGET: &str = "moon:generate"; @@ -23,21 +24,45 @@ pub struct GenerateOptions { pub vars: Vec, } -fn format_var_name(name: &str) -> String { - if name.starts_with("no-") { - name.strip_prefix("no-").unwrap().to_owned() - } else { - name.to_owned() - } +fn log_var(name: &str, value: &T, comment: Option<&str>) { + trace!( + target: LOG_TARGET, + "Setting variable {} to \"{}\" {}", + color::id(name), + value, + comment.unwrap_or_default(), + ); } fn parse_var_args(vars: &[String]) -> HashMap { let mut custom_vars = HashMap::new(); + if vars.is_empty() { + return custom_vars; + } + + debug!( + target: LOG_TARGET, + "Inheriting variable values from provided command line arguments" + ); + let lexer = clap_lex::RawArgs::new(vars); let mut cursor = lexer.cursor(); let mut previous_name: Option = None; + let mut set_var = |name: &str, value: String| { + let name = if let Some(stripped_name) = name.strip_prefix("no-") { + stripped_name + } else { + name + }; + let comment = color::muted_light(format!("(from --{})", name)); + + log_var(name, &value, Some(&comment)); + + custom_vars.insert(name.to_owned(), value); + }; + while let Some(arg) = lexer.next(&mut cursor) { // --name, --name=value if let Some((long, maybe_value)) = arg.to_long() { @@ -46,8 +71,8 @@ fn parse_var_args(vars: &[String]) -> HashMap { // If we found another long arg, but one previously exists, // this must be a boolean value! if let Some(name) = &previous_name { - custom_vars.insert( - format_var_name(name), + set_var( + name, if name.starts_with("no-") { "false".to_owned() } else { @@ -60,10 +85,7 @@ fn parse_var_args(vars: &[String]) -> HashMap { if let Some(value) = maybe_value { previous_name = None; - custom_vars.insert( - format_var_name(name), - value.to_str().unwrap_or_default().to_owned(), - ); + set_var(name, value.to_str().unwrap_or_default().to_owned()); // No value defined, so persist the name till the next iteration } else { @@ -91,8 +113,8 @@ fn parse_var_args(vars: &[String]) -> HashMap { } else if let Some(name) = previous_name { previous_name = None; - custom_vars.insert( - format_var_name(&name), + set_var( + &name, arg.to_value_os().to_str().unwrap_or_default().to_owned(), ); } @@ -109,6 +131,12 @@ fn gather_variables( let mut context = TemplateContext::new(); let custom_vars = parse_var_args(&options.vars); let error_handler = |e| GeneratorError::Moon(MoonError::Io(e)); + let default_comment = color::muted_light("(defaults)"); + + debug!( + target: LOG_TARGET, + "Declaring variable values from defaults and user prompts" + ); for (name, config) in &template.config.variables { match config { @@ -119,6 +147,8 @@ fn gather_variables( }; if options.defaults || var.prompt.is_none() { + log_var(name, &default, Some(&default_comment)); + context.insert(name, &default); } else { let value = Confirm::with_theme(theme) @@ -128,6 +158,8 @@ fn gather_variables( .interact() .map_err(error_handler)?; + log_var(name, &value, None); + context.insert(name, &value); } } @@ -139,6 +171,10 @@ fn gather_variables( .position(|i| i == default) .unwrap_or_default(); + if options.defaults { + log_var(name, &var.values[default_index], Some(&default_comment)); + } + match (options.defaults, var.multiple.unwrap_or_default()) { (true, true) => { context.insert(name, &[&var.values[default_index]]); @@ -159,14 +195,14 @@ fn gather_variables( ) .interact() .map_err(error_handler)?; + let value = indexes + .iter() + .map(|i| var.values[*i].clone()) + .collect::>(); - context.insert( - name, - &indexes - .iter() - .map(|i| var.values[*i].clone()) - .collect::>(), - ); + log_var(name, &map_list(&value, |f| f.to_string()), None); + + context.insert(name, &value); } (false, false) => { let index = Select::with_theme(theme) @@ -176,6 +212,8 @@ fn gather_variables( .interact() .map_err(error_handler)?; + log_var(name, &var.values[index], None); + context.insert(name, &var.values[index]); } }; @@ -190,6 +228,8 @@ fn gather_variables( }; if options.defaults || var.prompt.is_none() { + log_var(name, &default, Some(&default_comment)); + context.insert(name, &default); } else { let value: i32 = Input::with_theme(theme) @@ -207,6 +247,8 @@ fn gather_variables( .interact_text() .map_err(error_handler)?; + log_var(name, &value, None); + context.insert(name, &value); } } @@ -215,6 +257,8 @@ fn gather_variables( let default = custom_vars.get(name).unwrap_or(&var.default); if options.defaults || var.prompt.is_none() { + log_var(name, &default, Some(&default_comment)); + context.insert(name, &default); } else { let value: String = Input::with_theme(theme) @@ -232,6 +276,8 @@ fn gather_variables( .interact_text() .map_err(error_handler)?; + log_var(name, &value, None); + context.insert(name, &value); } } @@ -263,6 +309,10 @@ pub async fn generate( return Ok(()); } + if options.dry_run { + debug!(target: LOG_TARGET, "Running in DRY MODE"); + } + // Create the template instance let mut template = generator.load_template(name).await?; let term = Term::buffered_stdout(); @@ -284,13 +334,26 @@ pub async fn generate( // Determine the destination path let relative_dest = match &options.dest { Some(d) => d.clone(), - None => Input::with_theme(&theme) - .with_prompt("Where to generate code to?") - .allow_empty(false) - .interact_text()?, + None => { + trace!( + target: LOG_TARGET, + "Destination path not provided, prompting the user" + ); + + Input::with_theme(&theme) + .with_prompt("Where to generate code to?") + .allow_empty(false) + .interact_text()? + } }; let dest = path::normalize(cwd.join(&relative_dest)); + debug!( + target: LOG_TARGET, + "Destination path set to {}", + color::path(&dest) + ); + // Gather variables and build context let context = gather_variables(&template, &theme, &options)?; diff --git a/crates/cli/tests/generate_test.rs b/crates/cli/tests/generate_test.rs index 7278cb6aa9e..d66ed8ca3ef 100644 --- a/crates/cli/tests/generate_test.rs +++ b/crates/cli/tests/generate_test.rs @@ -192,3 +192,19 @@ fn errors_when_parsing_custom_var_types() { assert_snapshot!(get_path_safe_output(&assert)); } + +#[test] +fn supports_custom_filters() { + let fixture = create_sandbox("generator"); + + let assert = create_moon_command(fixture.path()) + .arg("generate") + .arg("vars") + .arg("./test") + .arg("--defaults") + .assert(); + + assert.success(); + + assert_snapshot!(fs::read_to_string(fixture.path().join("./test/filters.txt")).unwrap()); +} diff --git a/crates/cli/tests/snapshots/generate_test__interpolates_destination_path.snap b/crates/cli/tests/snapshots/generate_test__interpolates_destination_path.snap index 365c6fec2f0..07ca57cac1d 100644 --- a/crates/cli/tests/snapshots/generate_test__interpolates_destination_path.snap +++ b/crates/cli/tests/snapshots/generate_test__interpolates_destination_path.snap @@ -1,6 +1,6 @@ --- source: crates/cli/tests/generate_test.rs -assertion_line: 143 +assertion_line: 174 expression: get_path_safe_output(&assert) --- @@ -11,6 +11,7 @@ A template for testing all variable config combinations. created --> ./test/control.txt created --> ./test/expressions.txt created --> ./test/file-default-0.txt +created --> ./test/filters.txt diff --git a/crates/cli/tests/snapshots/generate_test__overwrites_existing_files_when_interpolated_path.snap b/crates/cli/tests/snapshots/generate_test__overwrites_existing_files_when_interpolated_path.snap index 8067b6282ab..cc05bde3e41 100644 --- a/crates/cli/tests/snapshots/generate_test__overwrites_existing_files_when_interpolated_path.snap +++ b/crates/cli/tests/snapshots/generate_test__overwrites_existing_files_when_interpolated_path.snap @@ -11,6 +11,7 @@ A template for testing all variable config combinations. replaced -> ./test/control.txt replaced -> ./test/expressions.txt replaced -> ./test/file-default-0.txt +replaced -> ./test/filters.txt diff --git a/crates/cli/tests/snapshots/generate_test__supports_custom_filters.snap b/crates/cli/tests/snapshots/generate_test__supports_custom_filters.snap new file mode 100644 index 00000000000..e08d1e1b92b --- /dev/null +++ b/crates/cli/tests/snapshots/generate_test__supports_custom_filters.snap @@ -0,0 +1,12 @@ +--- +source: crates/cli/tests/generate_test.rs +assertion_line: 209 +expression: "fs::read_to_string(fixture.path().join(\"./test/filters.txt\")).unwrap()" +--- +camel_case = someRandomValue +kebab_case = some-random-value +pascal_case = SomeRandomValue +snake_case = some_random_value +upper_kebab_case = SOME-RANDOM-VALUE +upper_snake_case = SOME_RANDOM_VALUE + diff --git a/crates/generator/Cargo.toml b/crates/generator/Cargo.toml index b13fb0b7f34..895b28b9221 100644 --- a/crates/generator/Cargo.toml +++ b/crates/generator/Cargo.toml @@ -7,9 +7,12 @@ edition = "2021" moon_config = { path = "../config" } moon_constants = { path = "../constants" } moon_error = { path = "../error" } +moon_logger = { path = "../logger" } moon_utils = { path = "../utils" } +convert_case = "0.6.0" futures = "0.3.24" lazy_static = "1.4.0" +serde_json = "1.0.85" tera = { version = "1.17.0", features = ["preserve_order"] } thiserror = "1.0.33" diff --git a/crates/generator/src/filters.rs b/crates/generator/src/filters.rs new file mode 100644 index 00000000000..85df2df0492 --- /dev/null +++ b/crates/generator/src/filters.rs @@ -0,0 +1,34 @@ +use convert_case::{Case, Casing}; +use serde_json::value::{to_value, Value}; +use std::collections::HashMap; +use tera::{try_get_value, Result}; + +fn to_case(case_fn: &str, case_type: Case, value: &Value) -> Result { + let s = try_get_value!(case_fn, "value", String, value); + + Ok(to_value(s.to_case(case_type)).unwrap()) +} + +pub fn camel_case(value: &Value, _: &HashMap) -> Result { + to_case("camel_case", Case::Camel, value) +} + +pub fn kebab_case(value: &Value, _: &HashMap) -> Result { + to_case("kebab_case", Case::Kebab, value) +} + +pub fn upper_kebab_case(value: &Value, _: &HashMap) -> Result { + to_case("upper_kebab_case", Case::UpperKebab, value) +} + +pub fn pascal_case(value: &Value, _: &HashMap) -> Result { + to_case("pascal_case", Case::Pascal, value) +} + +pub fn snake_case(value: &Value, _: &HashMap) -> Result { + to_case("snake_case", Case::Snake, value) +} + +pub fn upper_snake_case(value: &Value, _: &HashMap) -> Result { + to_case("upper_snake_case", Case::UpperSnake, value) +} diff --git a/crates/generator/src/generator.rs b/crates/generator/src/generator.rs index e5554130ec9..4ae917393f3 100644 --- a/crates/generator/src/generator.rs +++ b/crates/generator/src/generator.rs @@ -3,10 +3,13 @@ use crate::template::Template; use futures::stream::{FuturesUnordered, StreamExt}; use moon_config::{load_template_config_template, GeneratorConfig}; use moon_constants::CONFIG_TEMPLATE_FILENAME; -use moon_utils::{fs, regex::clean_id}; +use moon_logger::{color, debug, map_list, trace}; +use moon_utils::{fs, path, regex::clean_id}; use std::path::{Path, PathBuf}; use tera::Context; +const LOG_TARGET: &str = "moon:generator"; + pub struct Generator { config: GeneratorConfig, @@ -15,6 +18,8 @@ pub struct Generator { impl Generator { pub fn create(workspace_root: &Path, config: &GeneratorConfig) -> Result { + debug!(target: LOG_TARGET, "Creating generator"); + Ok(Generator { config: config.to_owned(), workspace_root: workspace_root.to_path_buf(), @@ -34,6 +39,13 @@ impl Generator { return Err(GeneratorError::ExistingTemplate(name, root)); } + debug!( + target: LOG_TARGET, + "Creating new template {} at {}", + color::id(&name), + color::path(&root) + ); + fs::create_dir_all(&root).await?; fs::write( @@ -50,10 +62,19 @@ impl Generator { pub async fn load_template(&self, name: &str) -> Result { let name = clean_id(name); + trace!( + target: LOG_TARGET, + "Finding template {} from configured locations: {}", + color::id(&name), + map_list(&self.config.templates, |t| color::file(t)) + ); + for template_path in &self.config.templates { - let root = self.workspace_root.join(template_path).join(&name); + let root = path::normalize(self.workspace_root.join(template_path).join(&name)); if root.exists() { + trace!(target: LOG_TARGET, "Found at {}", color::path(&root)); + return Template::new(name, root); } } @@ -68,6 +89,12 @@ impl Generator { ) -> Result<(), GeneratorError> { let mut futures = FuturesUnordered::new(); + debug!( + target: LOG_TARGET, + "Generating template {} files", + color::id(&template.name), + ); + for file in &template.files { if file.should_write() { futures.push(async { template.render_file(file, context).await }); @@ -83,6 +110,8 @@ impl Generator { } } + debug!(target: LOG_TARGET, "Generation complete!"); + Ok(()) } } diff --git a/crates/generator/src/lib.rs b/crates/generator/src/lib.rs index d7fc5b6223e..cb4d39d6356 100644 --- a/crates/generator/src/lib.rs +++ b/crates/generator/src/lib.rs @@ -1,4 +1,5 @@ mod errors; +mod filters; mod generator; mod template; diff --git a/crates/generator/src/template.rs b/crates/generator/src/template.rs index e36896f2415..70e57f94a74 100644 --- a/crates/generator/src/template.rs +++ b/crates/generator/src/template.rs @@ -1,7 +1,9 @@ +use crate::filters; use crate::GeneratorError; use lazy_static::lazy_static; use moon_config::{format_error_line, format_figment_errors, ConfigError, TemplateConfig}; use moon_constants::CONFIG_TEMPLATE_FILENAME; +use moon_logger::{color, debug, trace}; use moon_utils::{fs, path, regex}; use std::path::{Path, PathBuf}; use tera::{Context, Tera}; @@ -10,6 +12,8 @@ lazy_static! { pub static ref PATH_VAR: regex::Regex = regex::create_regex(r#"\[([A-Za-z0-9_]+)\]"#).unwrap(); } +const LOG_TARGET: &str = "moon:generator:template"; + #[derive(Debug, Eq, PartialEq)] pub enum FileState { Created, @@ -64,6 +68,13 @@ pub struct Template { impl Template { pub fn new(name: String, root: PathBuf) -> Result { + debug!( + target: LOG_TARGET, + "Loading template {} from {}", + color::id(&name), + color::path(&root) + ); + let config = match TemplateConfig::load(root.join(CONFIG_TEMPLATE_FILENAME)) { Ok(cfg) => cfg, Err(errors) => { @@ -77,9 +88,17 @@ impl Template { } }; + let mut engine = Tera::default(); + engine.register_filter("camel_case", filters::camel_case); + engine.register_filter("kebab_case", filters::kebab_case); + engine.register_filter("pascal_case", filters::pascal_case); + engine.register_filter("snake_case", filters::snake_case); + engine.register_filter("upper_kebab_case", filters::upper_kebab_case); + engine.register_filter("upper_snake_case", filters::upper_snake_case); + Ok(Template { config, - engine: Tera::default(), + engine, files: vec![], name, root, @@ -114,6 +133,13 @@ impl Template { continue; } + trace!( + target: LOG_TARGET, + "Loading template file {} (source = {})", + color::file(&name), + color::path(&source_path), + ); + files.push(TemplateFile { dest_path, existed, @@ -138,6 +164,25 @@ impl Template { file: &TemplateFile, context: &Context, ) -> Result<(), GeneratorError> { + match file.state() { + FileState::Replaced => { + trace!( + target: LOG_TARGET, + "Overwriting template file {} (destination = {})", + color::file(&file.name), + color::path(&file.dest_path) + ); + } + _ => { + trace!( + target: LOG_TARGET, + "Writing template file {} (destination = {})", + color::file(&file.name), + color::path(&file.dest_path) + ); + } + } + fs::create_dir_all(file.dest_path.parent().unwrap()).await?; fs::write( diff --git a/tests/fixtures/generator/templates/vars/filters.txt b/tests/fixtures/generator/templates/vars/filters.txt new file mode 100644 index 00000000000..11c38eca4ce --- /dev/null +++ b/tests/fixtures/generator/templates/vars/filters.txt @@ -0,0 +1,6 @@ +camel_case = {{ "some random value" | camel_case }} +kebab_case = {{ "some random value" | kebab_case }} +pascal_case = {{ "some random value" | pascal_case }} +snake_case = {{ "some random value" | snake_case }} +upper_kebab_case = {{ "some random value" | upper_kebab_case }} +upper_snake_case = {{ "some random value" | upper_snake_case }} diff --git a/website/docs/commands/generate.mdx b/website/docs/commands/generate.mdx index 4fdb5d1c642..8948c91d590 100644 --- a/website/docs/commands/generate.mdx +++ b/website/docs/commands/generate.mdx @@ -2,9 +2,13 @@ title: generate --- +import VersionLabel from '@site/src/components/Docs/VersionLabel'; + + + The `moon generate ` (or `moon g`) command will generate code (files and folders) from a -pre-defined template of the same name. Templates are located based on the -[`generator.templates`](../config/workspace#templates) setting. +pre-defined template of the same name, using an interactive series of prompts. Templates are located +based on the [`generator.templates`](../config/workspace#templates) setting. ```shell # Generate code from a template to a target directory diff --git a/website/docs/guides/codegen.mdx b/website/docs/guides/codegen.mdx index 4d5102344f2..445c4419128 100644 --- a/website/docs/guides/codegen.mdx +++ b/website/docs/guides/codegen.mdx @@ -1,7 +1,21 @@ --- title: Generating code +toc_max_heading_level: 6 +tags: [codegen, generator, scaffold, template] --- +import VersionLabel from '@site/src/components/Docs/VersionLabel'; + + + +Code generation provides an easy mechanism for automating common development workflows and file +structures. Whether it's scaffolding a new library or application, updating configuration, or +standardizing patterns. + +To accomplish this, we provide a generator, which is divided into two parts. The first being the +templates and their files to be scaffolded. The second is our rendering engine that writes template +files to a destination. + ## Creating a new template To create a new template, run [`moon generate`][command] while passing the `--template` option. This @@ -43,10 +57,22 @@ said, there are a few unique scenarios to be aware of! a template file `src/[type].ts`, and a variable `type` with a value of "bin", then the destination file path would be `src/bin.ts`. +An example of the templates folder structure may look something like the following: + +``` +templates/ +├── npm-package/ +│ ├── src/ +│ ├── tests/ +│ ├── package.json +│ └── template.yml +└── react-app/ +``` + ### Template engine & syntax Rendering templates is powered by [Tera](https://tera.netlify.app/), a Rust based template engine -with syntax similar to Twig, Liquid, Jinja, and more. We highly encourage everyone to read Tera's +with syntax similar to Twig, Liquid, Django, and more. We highly encourage everyone to read Tera's documentation for an in-depth understanding, but as a quick reference, Tera supports the following: - [Variable interpolation](https://tera.netlify.app/docs/#variables) (defined with the @@ -79,6 +105,67 @@ documentation for an in-depth understanding, but as a quick reference, Tera supp - And many more features, like auto-escaping, white space control, math operators! +#### Filters + +Filters are a mechanism for transforming values during interpolation and are written using pipes +(`|`). Tera provides many [built-in filters](https://tera.netlify.app/docs/#built-in-filters), but +we also provide the following custom filters: + +- Strings - `camel_case`, `pascal_case`, `snake_case`, `upper_snake_case`, `kebab_case`, + `upper_kebab_case` + +## Generating code from a template + +Once a template has been created and configured, you can generate files based on it using the +[`moon generate`][command] command! This is also know as scaffolding or code generation. + +This command requires the name of a template as the 1st argument. The template name is the folder +name on the file system that houses all the template files. + +```shell +$ moon generate npm-package +``` + +An optional destination path, relative from the current working directory, can be provided as the +2nd argument. If not provided, you'll be prompted during generation to provide one. + +```shell +$ moon generate npm-package ./packages/example +``` + +> This command is extremely interactive, as we'll prompt you for the destination path, variable +> values, whether to overwrite files, and more. If you'd prefer to avoid interactions, pass +> `--defaults`, or `--force`, or both. + +### Configuring template locations + +Templates can be located anywhere, especially when [being shared](#sharing-templates). Because of +this, our generator will loop through all template paths configured in +[`generator.templates`][gen-templates], in order, until a match is found (template name matches the +folder name). + +```yaml title=".moon/workspace.yml" +generator: + templates: + - './templates' + - './other/templates' +``` + +### Declaring variables with CLI arguments + +During generation, you'll be prompted in the terminal to provide a value for any configured +variables. However, you can pre-fill these variable values by passing arbitray command line +arguments after `--` to [`moon generate`][command]. Argument names must exactly match the variable +names. + +Using the package template example above, we could pre-fill the `name` variable like so: + +```shell +$ moon generate npm-package ./packages/example -- --name '@company/example' --private +``` + +> Boolean variables can be negated by prefixing the argument with `--no-`. + ## Sharing templates Although moon is designed for a monorepo, you may be using multiple repositories and would like to diff --git a/website/docs/guides/profile.mdx b/website/docs/guides/profile.mdx index 8f89c9bc561..6c6900b102e 100644 --- a/website/docs/guides/profile.mdx +++ b/website/docs/guides/profile.mdx @@ -3,6 +3,9 @@ title: Profiling tasks --- import Image from '@site/src/components/Docs/Image'; +import VersionLabel from '@site/src/components/Docs/VersionLabel'; + + Troubleshooting slow or unperformant tasks? Profile and diagnose them with ease! diff --git a/website/docs/guides/root-project.mdx b/website/docs/guides/root-project.mdx index 07b5bb551a2..78c364fe64d 100644 --- a/website/docs/guides/root-project.mdx +++ b/website/docs/guides/root-project.mdx @@ -2,6 +2,10 @@ title: Root-level project --- +import VersionLabel from '@site/src/components/Docs/VersionLabel'; + + + Coming from other repositories or build systems, you may be familiar with tasks available at the repository root, in which one-off, organization, maintenance, or process oriented tasks can be ran. moon supports this through a concept known as a root-level project. diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 73200921ee9..2eff0df58b9 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -185,7 +185,7 @@ const config = { prism: { theme: prismTheme, darkTheme: prismTheme, - // additionalLanguages: ['twig'], + additionalLanguages: ['twig'], }, }), diff --git a/website/src/theme/prism-include-languages.js b/website/src/theme/prism-include-languages.js new file mode 100644 index 00000000000..19d40d97214 --- /dev/null +++ b/website/src/theme/prism-include-languages.js @@ -0,0 +1,26 @@ +/* eslint-disable node/no-unsupported-features/es-builtins */ +import siteConfig from '@generated/docusaurus.config'; + +export default function prismIncludeLanguages(PrismObject) { + const { + themeConfig: { prism }, + } = siteConfig; + const { additionalLanguages } = prism; + + // Prism components work on the Prism instance on the window, while prism- + // react-renderer uses its own Prism instance. We temporarily mount the + // instance onto window, import components to enhance it, then remove it to + // avoid polluting global namespace. + // You can mutate PrismObject: registering plugins, deleting languages... As + // long as you don't re-assign it + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + globalThis.Prism = PrismObject; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call + additionalLanguages.forEach((lang) => { + require(`prismjs/components/prism-${lang}`); + }); + + // We need to keep the global around so that the `twig` language works! + // delete globalThis.Prism; +}