diff --git a/crates/app/src/commands/docker/prune.rs b/crates/app/src/commands/docker/prune.rs index e0d9c339a50..a8925679fcc 100644 --- a/crates/app/src/commands/docker/prune.rs +++ b/crates/app/src/commands/docker/prune.rs @@ -12,56 +12,70 @@ use moon_tool::DependencyManager; use rustc_hash::FxHashSet; use starbase::AppResult; use starbase_utils::{fs, json}; -use std::path::Path; use tracing::{debug, instrument}; #[instrument(skip_all)] pub async fn prune_bun( bun: &BunTool, - workspace_root: &Path, + session: &CliSession, project_graph: &ProjectGraph, manifest: &DockerManifest, ) -> AppResult { - let mut package_names = vec![]; + // Some package managers do not delete stale node modules + if session + .workspace_config + .docker + .prune + .delete_vendor_directories + { + debug!("Removing Bun vendor directories (node_modules)"); + + fs::remove_dir_all(session.workspace_root.join("node_modules"))?; + + for source in project_graph.sources().values() { + fs::remove_dir_all(source.join("node_modules").to_path(&session.workspace_root))?; + } + } - for project_id in &manifest.focused_projects { - if let Some(source) = project_graph.sources().get(project_id) { - if let Some(package_json) = PackageJsonCache::read(source.to_path(workspace_root))? { - if let Some(package_name) = package_json.data.name { - package_names.push(package_name); + // Install production only dependencies for focused projects + if session.workspace_config.docker.prune.install_toolchain_deps { + let mut package_names = vec![]; + + for project_id in &manifest.focused_projects { + if let Some(source) = project_graph.sources().get(project_id) { + if let Some(package_json) = + PackageJsonCache::read(source.to_path(&session.workspace_root))? + { + if let Some(package_name) = package_json.data.name { + package_names.push(package_name); + } } } } - } - - debug!( - packages = ?package_names, - "Pruning Bun dependencies (node_modules)" - ); - // Some package managers do not delete stale node modules - fs::remove_dir_all(workspace_root.join("node_modules"))?; + debug!( + packages = ?package_names, + "Pruning Bun dependencies" + ); - for source in project_graph.sources().values() { - fs::remove_dir_all(source.join("node_modules").to_path(workspace_root))?; + bun.install_focused_dependencies(&(), &package_names, true) + .await?; } - // Install production only dependencies for focused projects - bun.install_focused_dependencies(&(), &package_names, true) - .await?; - Ok(()) } #[instrument(skip_all)] pub async fn prune_deno( deno: &DenoTool, - _workspace_root: &Path, + session: &CliSession, _project_graph: &ProjectGraph, _manifest: &DockerManifest, ) -> AppResult { // noop - deno.install_focused_dependencies(&(), &[], true).await?; + if session.workspace_config.docker.prune.install_toolchain_deps { + deno.install_focused_dependencies(&(), &[], true).await?; + } Ok(()) } @@ -69,56 +83,76 @@ pub async fn prune_deno( #[instrument(skip_all)] pub async fn prune_node( node: &NodeTool, - workspace_root: &Path, + session: &CliSession, project_graph: &ProjectGraph, manifest: &DockerManifest, ) -> AppResult { - let mut package_names = vec![]; + // Some package managers do not delete stale node modules + if session + .workspace_config + .docker + .prune + .delete_vendor_directories + { + debug!("Removing Node.js vendor directories (node_modules)"); + + fs::remove_dir_all(session.workspace_root.join("node_modules"))?; + + for source in project_graph.sources().values() { + fs::remove_dir_all(source.join("node_modules").to_path(&session.workspace_root))?; + } + } - for project_id in &manifest.focused_projects { - if let Some(source) = project_graph.sources().get(project_id) { - if let Some(package_json) = PackageJsonCache::read(source.to_path(workspace_root))? { - if let Some(package_name) = package_json.data.name { - package_names.push(package_name); + // Install production only dependencies for focused projects + if session.workspace_config.docker.prune.install_toolchain_deps { + let mut package_names = vec![]; + + for project_id in &manifest.focused_projects { + if let Some(source) = project_graph.sources().get(project_id) { + if let Some(package_json) = + PackageJsonCache::read(source.to_path(&session.workspace_root))? + { + if let Some(package_name) = package_json.data.name { + package_names.push(package_name); + } } } } - } - debug!( - packages = ?package_names, - "Pruning Node.js dependencies (node_modules)" - ); - - // Some package managers do not delete stale node modules - fs::remove_dir_all(workspace_root.join("node_modules"))?; + debug!( + packages = ?package_names, + "Pruning Node.js dependencies" + ); - for source in project_graph.sources().values() { - fs::remove_dir_all(source.join("node_modules").to_path(workspace_root))?; + node.get_package_manager() + .install_focused_dependencies(node, &package_names, true) + .await?; } - // Install production only dependencies for focused projects - node.get_package_manager() - .install_focused_dependencies(node, &package_names, true) - .await?; - Ok(()) } // This assumes that the project was built in --release mode. Is this correct? #[instrument(skip_all)] -pub async fn prune_rust(_rust: &RustTool, workspace_root: &Path) -> AppResult { - let target_dir = workspace_root.join("target"); - let lockfile_path = workspace_root.join("Cargo.lock"); - - // Only delete target if relative to `Cargo.lock` - if target_dir.exists() && lockfile_path.exists() { - debug!( - target_dir = ?target_dir, - "Deleting Rust target directory" - ); - - fs::remove_dir_all(target_dir)?; +pub async fn prune_rust(_rust: &RustTool, session: &CliSession) -> AppResult { + if session + .workspace_config + .docker + .prune + .delete_vendor_directories + { + let target_dir = &session.workspace_root.join("target"); + let lockfile_path = &session.workspace_root.join("Cargo.lock"); + + // Only delete target if relative to `Cargo.lock` + if target_dir.exists() && lockfile_path.exists() { + debug!( + target_dir = ?target_dir, + "Deleting Rust target directory" + ); + + fs::remove_dir_all(target_dir)?; + } } Ok(()) @@ -162,7 +196,7 @@ pub async fn prune(session: CliSession) -> AppResult { .as_any() .downcast_ref::() .unwrap(), - &session.workspace_root, + &session, &project_graph, &manifest, ) @@ -175,7 +209,7 @@ pub async fn prune(session: CliSession) -> AppResult { .as_any() .downcast_ref::() .unwrap(), - &session.workspace_root, + &session, &project_graph, &manifest, ) @@ -188,7 +222,7 @@ pub async fn prune(session: CliSession) -> AppResult { .as_any() .downcast_ref::() .unwrap(), - &session.workspace_root, + &session, &project_graph, &manifest, ) @@ -201,7 +235,7 @@ pub async fn prune(session: CliSession) -> AppResult { .as_any() .downcast_ref::() .unwrap(), - &session.workspace_root, + &session, ) .await?; } diff --git a/crates/app/src/commands/docker/scaffold.rs b/crates/app/src/commands/docker/scaffold.rs index 0b70d0d655e..d36bea3ea22 100644 --- a/crates/app/src/commands/docker/scaffold.rs +++ b/crates/app/src/commands/docker/scaffold.rs @@ -13,7 +13,7 @@ use schematic::ConfigEnum; use starbase::AppResult; use starbase_styles::color; use starbase_utils::{fs, glob, json}; -use std::path::Path; +use std::path::{Path, PathBuf}; use tracing::{debug, instrument, warn}; #[derive(Args, Clone, Debug)] @@ -25,6 +25,16 @@ pub struct DockerScaffoldArgs { include: Vec, } +fn copy_files_from_paths(paths: Vec, source: &Path, dest: &Path) -> AppResult { + let mut files = vec![]; + + for file in paths { + files.push(path::to_string(file.strip_prefix(source).unwrap())?); + } + + copy_files(&files, source, dest) +} + fn copy_files>(list: &[T], source: &Path, dest: &Path) -> AppResult { for file in list { let file = file.as_ref(); @@ -89,56 +99,69 @@ async fn scaffold_workspace( ]; let mut files_to_create: Vec = vec![]; - for lang in LanguageType::variants() { - files_to_copy.extend(detect_language_files(&lang)); - - // These are special cases - match lang { - LanguageType::JavaScript => { - files_to_copy.extend([ - "postinstall.js".into(), - "postinstall.cjs".into(), - "postinstall.mjs".into(), - ]); - } - LanguageType::Rust => { - if let Some(cargo_toml) = CargoTomlCache::read(source)? { - let manifests = cargo_toml.get_member_manifest_paths(source)?; - - // Non-workspace - if manifests.is_empty() { - if lang == project_lang { - files_to_create.extend(["src/lib.rs".into(), "src/main.rs".into()]); + if session + .workspace_config + .docker + .scaffold + .copy_toolchain_files + { + for lang in LanguageType::variants() { + files_to_copy.extend(detect_language_files(&lang)); + + // These are special cases + match lang { + LanguageType::JavaScript => { + files_to_copy.extend([ + "postinstall.js".into(), + "postinstall.cjs".into(), + "postinstall.mjs".into(), + ]); + } + LanguageType::Rust => { + if let Some(cargo_toml) = CargoTomlCache::read(source)? { + let manifests = cargo_toml.get_member_manifest_paths(source)?; + + // Non-workspace + if manifests.is_empty() { + if lang == project_lang { + files_to_create + .extend(["src/lib.rs".into(), "src/main.rs".into()]); + } } - } - // Workspace - else { - for manifest in manifests { - if let Ok(rel_manifest) = manifest.strip_prefix(source) { - files_to_copy.push(path::to_string(rel_manifest)?); - - let rel_manifest_dir = rel_manifest.parent().unwrap(); - - if lang == project_lang { - files_to_create.extend([ - path::to_string(rel_manifest_dir.join("src/lib.rs"))?, - path::to_string(rel_manifest_dir.join("src/main.rs"))?, - ]); + // Workspace + else { + for manifest in manifests { + if let Ok(rel_manifest) = manifest.strip_prefix(source) { + files_to_copy.push(path::to_string(rel_manifest)?); + + let rel_manifest_dir = rel_manifest.parent().unwrap(); + + if lang == project_lang { + files_to_create.extend([ + path::to_string( + rel_manifest_dir.join("src/lib.rs"), + )?, + path::to_string( + rel_manifest_dir.join("src/main.rs"), + )?, + ]); + } } } } } } - } - LanguageType::TypeScript => { - if let Some(typescript_config) = &session.toolchain_config.typescript { - files_to_copy.push(typescript_config.project_config_file_name.to_owned()); - files_to_copy.push(typescript_config.root_config_file_name.to_owned()); - files_to_copy - .push(typescript_config.root_options_config_file_name.to_owned()); + LanguageType::TypeScript => { + if let Some(typescript_config) = &session.toolchain_config.typescript { + files_to_copy + .push(typescript_config.project_config_file_name.to_owned()); + files_to_copy.push(typescript_config.root_config_file_name.to_owned()); + files_to_copy + .push(typescript_config.root_options_config_file_name.to_owned()); + } } + _ => {} } - _ => {} } } @@ -176,33 +199,40 @@ async fn scaffold_workspace( )?; } - if let Some(js_config) = &session.toolchain_config.bun { - if js_config.packages_root != "." { - copy_from_dir( - &session.workspace_root.join(&js_config.packages_root), - &docker_workspace_root.join(&js_config.packages_root), - LanguageType::Unknown, - )?; + if session + .workspace_config + .docker + .scaffold + .copy_toolchain_files + { + if let Some(js_config) = &session.toolchain_config.bun { + if js_config.packages_root != "." { + copy_from_dir( + &session.workspace_root.join(&js_config.packages_root), + &docker_workspace_root.join(&js_config.packages_root), + LanguageType::Unknown, + )?; + } } - } - if let Some(js_config) = &session.toolchain_config.node { - if js_config.packages_root != "." { - copy_from_dir( - &session.workspace_root.join(&js_config.packages_root), - &docker_workspace_root.join(&js_config.packages_root), - LanguageType::Unknown, - )?; + if let Some(js_config) = &session.toolchain_config.node { + if js_config.packages_root != "." { + copy_from_dir( + &session.workspace_root.join(&js_config.packages_root), + &docker_workspace_root.join(&js_config.packages_root), + LanguageType::Unknown, + )?; + } } - } - if let Some(typescript_config) = &session.toolchain_config.typescript { - if typescript_config.root != "." { - copy_from_dir( - &session.workspace_root.join(&typescript_config.root), - &docker_workspace_root.join(&typescript_config.root), - LanguageType::Unknown, - )?; + if let Some(typescript_config) = &session.toolchain_config.typescript { + if typescript_config.root != "." { + copy_from_dir( + &session.workspace_root.join(&typescript_config.root), + &docker_workspace_root.join(&typescript_config.root), + LanguageType::Unknown, + )?; + } } } @@ -215,17 +245,28 @@ async fn scaffold_workspace( "Copying core moon configuration" ); - let moon_configs = glob::walk(moon_dir, ["*.yml", "tasks/**/*.yml"])? - .into_iter() - .map(|f| path::to_string(f.strip_prefix(&session.workspace_root).unwrap())) - .collect::, _>>()?; - - copy_files( - &moon_configs, + copy_files_from_paths( + glob::walk_files(moon_dir, ["*.yml", "tasks/**/*.yml"])?, &session.workspace_root, &docker_workspace_root, )?; + // Include via globs + let include = &session.workspace_config.docker.scaffold.include; + + if !include.is_empty() { + debug!( + include = ?include, + "Including additional files" + ); + + copy_files_from_paths( + glob::walk_files(&session.workspace_root, include)?, + &session.workspace_root, + &docker_workspace_root, + )?; + } + Ok(()) } @@ -239,29 +280,41 @@ async fn scaffold_sources_project( manifest: &mut DockerManifest, ) -> AppResult { let project = project_graph.get(project_id)?; + let mut include_globs = vec!["!node_modules/**", "!target/**/*", "!vendor/**"]; + + manifest.focused_projects.insert(project_id.to_owned()); + + if project.config.docker.scaffold.include.is_empty() { + include_globs.push("**/*"); + } else { + include_globs.extend( + project + .config + .docker + .scaffold + .include + .iter() + .map(|glob| glob.as_str()), + ); + } debug!( id = project_id.as_str(), + globs = ?include_globs, "Copying sources from project {}", color::id(project_id), ); - manifest.focused_projects.insert(project_id.to_owned()); - - for file in glob::walk_files( - &project.root, - ["**/*", "!node_modules/**", "!target/**/*", "!vendor/**"], - )? { - fs::copy_file( - &file, - docker_sources_root.join(file.strip_prefix(&session.workspace_root).unwrap()), - )?; - } + copy_files_from_paths( + glob::walk_files(&project.root, include_globs)?, + &session.workspace_root, + docker_sources_root, + )?; for dep_cfg in &project.dependencies { // Avoid root-level projects as it will pull in all sources, // which is usually not what users want. If they do want it, - // they can be explicitly on the command line! + // they can be explicit in config or on the command line! if !dep_cfg.is_root_scope() { debug!( id = project_id.as_str(), @@ -296,7 +349,7 @@ async fn scaffold_sources( debug!( scaffold_dir = ?docker_sources_root, projects = ?project_ids.iter().map(|id| id.as_str()).collect::>(), - "Scaffolding sources skeleton, including source files from focused projects" + "Scaffolding sources skeleton, including files from focused projects" ); let mut manifest = DockerManifest { @@ -325,17 +378,21 @@ async fn scaffold_sources( // Include via globs if !include.is_empty() { + warn!( + "The --include argument is deprecated, use the {} settings instead", + color::property("docker") + ); + debug!( include = ?include, "Including additional sources" ); - let files = glob::walk_files(&session.workspace_root, include)? - .into_iter() - .map(|f| path::to_string(f.strip_prefix(&session.workspace_root).unwrap())) - .collect::, _>>()?; - - copy_files(&files, &session.workspace_root, &docker_sources_root)?; + copy_files_from_paths( + glob::walk_files(&session.workspace_root, include)?, + &session.workspace_root, + &docker_sources_root, + )?; } json::write_file(docker_sources_root.join(MANIFEST_NAME), &manifest, true)?; @@ -364,14 +421,16 @@ pub fn check_docker_ignore(workspace_root: &Path) -> miette::Result<()> { // Check lines so we can match exactly and avoid comments or nested paths for line in ignore.lines() { - match line.trim() { - // Better way? - ".moon/cache" | ".moon/cache/" | "./.moon/cache" | "./.moon/cache/" => { - is_ignored = true; - break; - } - _ => {} - }; + if line + .trim() + .trim_start_matches("./") + .trim_start_matches('/') + .trim_end_matches('/') + == ".moon/cache" + { + is_ignored = true; + break; + } } } diff --git a/crates/cli/tests/docker_test.rs b/crates/cli/tests/docker_test.rs index 45c52cc6398..66b224f0819 100644 --- a/crates/cli/tests/docker_test.rs +++ b/crates/cli/tests/docker_test.rs @@ -446,9 +446,11 @@ mod prune_node { write_manifest(sandbox.path(), "other"); - sandbox.run_moon(|cmd| { - cmd.arg("docker").arg("prune"); - }); + sandbox + .run_moon(|cmd| { + cmd.arg("docker").arg("prune"); + }) + .debug(); // should exist assert!(sandbox.path().join("node_modules/solid-js").exists()); diff --git a/crates/cli/tests/snapshots/run_system_test__windows__retries_on_failure_till_count.snap b/crates/cli/tests/snapshots/run_system_test__windows__retries_on_failure_till_count.snap index c023a675cd5..04f06b8b783 100644 --- a/crates/cli/tests/snapshots/run_system_test__windows__retries_on_failure_till_count.snap +++ b/crates/cli/tests/snapshots/run_system_test__windows__retries_on_failure_till_count.snap @@ -10,7 +10,7 @@ stdout stdout ▪▪▪▪ windows:retryCount (attempt 4/4) stdout -▪▪▪▪ windows:retryCount (100ms) +▪▪▪▪ windows:retryCount (attempt 4/4, 100ms) stderr stderr stderr diff --git a/crates/config/src/project/docker_config.rs b/crates/config/src/project/docker_config.rs new file mode 100644 index 00000000000..2bcd11936af --- /dev/null +++ b/crates/config/src/project/docker_config.rs @@ -0,0 +1,23 @@ +use crate::portable_path::GlobPath; +use moon_common::cacheable; +use schematic::Config; + +cacheable!( + /// Configures aspects of the Docker scaffolding process. + #[derive(Clone, Config, Debug, Eq, PartialEq)] + pub struct ProjectDockerScaffoldConfig { + /// List of glob patterns, relative from the project root, + /// to include (or exclude) in the sources skeleton. + pub include: Vec, + } +); + +cacheable!( + /// Configures our Docker integration. + #[derive(Clone, Config, Debug, Eq, PartialEq)] + pub struct ProjectDockerConfig { + /// Configures aspects of the Docker scaffolding process. + #[setting(nested)] + pub scaffold: ProjectDockerScaffoldConfig, + } +); diff --git a/crates/config/src/project/mod.rs b/crates/config/src/project/mod.rs index 42d01fd1991..899386c0eb7 100644 --- a/crates/config/src/project/mod.rs +++ b/crates/config/src/project/mod.rs @@ -1,10 +1,12 @@ mod dep_config; +mod docker_config; mod overrides_config; mod owners_config; mod task_config; mod task_options_config; pub use dep_config::*; +pub use docker_config::*; pub use overrides_config::*; pub use owners_config::*; pub use task_config::*; diff --git a/crates/config/src/project_config.rs b/crates/config/src/project_config.rs index 2b081d024f4..c23b5cd6862 100644 --- a/crates/config/src/project_config.rs +++ b/crates/config/src/project_config.rs @@ -109,6 +109,10 @@ cacheable!( #[setting(nested)] pub depends_on: Vec, + /// Configures Docker integration for this project. + #[setting(nested)] + pub docker: ProjectDockerConfig, + /// A mapping of environment variables that will be set for /// all tasks within the project. pub env: FxHashMap, diff --git a/crates/config/src/workspace/docker_config.rs b/crates/config/src/workspace/docker_config.rs new file mode 100644 index 00000000000..b230efa50ca --- /dev/null +++ b/crates/config/src/workspace/docker_config.rs @@ -0,0 +1,48 @@ +use crate::portable_path::GlobPath; +use moon_common::cacheable; +use schematic::Config; + +cacheable!( + /// Configures aspects of the Docker pruning process. + #[derive(Clone, Config, Debug, Eq, PartialEq)] + pub struct DockerPruneConfig { + /// Automatically delete vendor directories (package manager + /// dependencies, build targets, etc) while pruning. + #[setting(default = true)] + pub delete_vendor_directories: bool, + + /// Automatically install production dependencies for all required + /// toolchain's of the focused projects within the Docker build. + #[setting(default = true)] + pub install_toolchain_deps: bool, + } +); + +cacheable!( + /// Configures aspects of the Docker scaffolding process. + #[derive(Clone, Config, Debug, Eq, PartialEq)] + pub struct DockerScaffoldConfig { + /// Copy toolchain specific configs/manifests/files into + /// the workspace skeleton. + #[setting(default = true)] + pub copy_toolchain_files: bool, + + /// List of glob patterns, relative from the workspace root, + /// to include (or exclude) in the workspace skeleton. + pub include: Vec, + } +); + +cacheable!( + /// Configures our Docker integration. + #[derive(Clone, Config, Debug, Eq, PartialEq)] + pub struct DockerConfig { + /// Configures aspects of the Docker pruning process. + #[setting(nested)] + pub prune: DockerPruneConfig, + + /// Configures aspects of the Docker scaffolding process. + #[setting(nested)] + pub scaffold: DockerScaffoldConfig, + } +); diff --git a/crates/config/src/workspace/mod.rs b/crates/config/src/workspace/mod.rs index e0ab65bab17..1bb963b5bc7 100644 --- a/crates/config/src/workspace/mod.rs +++ b/crates/config/src/workspace/mod.rs @@ -1,5 +1,6 @@ mod codeowners_config; mod constraints_config; +mod docker_config; mod experiments_config; mod generator_config; mod hasher_config; @@ -10,6 +11,7 @@ mod vcs_config; pub use codeowners_config::*; pub use constraints_config::*; +pub use docker_config::*; pub use experiments_config::*; pub use generator_config::*; pub use hasher_config::*; diff --git a/crates/config/src/workspace_config.rs b/crates/config/src/workspace_config.rs index acd91ed7e1a..696f5e19e6c 100644 --- a/crates/config/src/workspace_config.rs +++ b/crates/config/src/workspace_config.rs @@ -115,6 +115,10 @@ pub struct WorkspaceConfig { #[setting(nested)] pub constraints: ConstraintsConfig, + /// Configures Docker integration for the workspace. + #[setting(nested)] + pub docker: DockerConfig, + /// Configures experiments across the entire moon workspace. #[setting(nested)] pub experiments: ExperimentsConfig, diff --git a/crates/config/tests/project_config_test.rs b/crates/config/tests/project_config_test.rs index e5b0124c9e4..743991fab26 100644 --- a/crates/config/tests/project_config_test.rs +++ b/crates/config/tests/project_config_test.rs @@ -14,7 +14,7 @@ mod project_config { #[test] #[should_panic( - expected = "unknown field `unknown`, expected one of `$schema`, `dependsOn`, `env`, `fileGroups`, `id`, `language`, `owners`, `platform`, `project`, `stack`, `tags`, `tasks`, `toolchain`, `type`, `workspace`" + expected = "unknown field `unknown`, expected one of `$schema`, `dependsOn`, `docker`, `env`, `fileGroups`, `id`, `language`, `owners`, `platform`, `project`, `stack`, `tags`, `tasks`, `toolchain`, `type`, `workspace`" )] fn error_unknown_field() { test_load_config(CONFIG_PROJECT_FILENAME, "unknown: 123", |path| { diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 1b4f27717bb..cdd6bcc186b 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -4,6 +4,8 @@ #### 🚀 Updates +- Added new `docker` settings to both `.moon/workspace.yml` and `moon.yml`, allowing it to be + configured at the workspace and project levels. - Added support for [murex](https://murex.rocks/) shells. - Updated both `unixShell` and `windowsShell` task options. - We now generate JSON schemas for our configuration files to `.moon/cache/schemas`, so that they diff --git a/packages/types/src/project-config.ts b/packages/types/src/project-config.ts index 1144ba5ba8e..dbfbe0c81e7 100644 --- a/packages/types/src/project-config.ts +++ b/packages/types/src/project-config.ts @@ -2,8 +2,8 @@ /* eslint-disable */ -import type { UnresolvedVersionSpec } from './toolchain-config'; import type { PartialTaskConfig, PlatformType, TaskConfig } from './tasks-config'; +import type { UnresolvedVersionSpec } from './toolchain-config'; /** The scope and or relationship of the dependency. */ export type DependencyScope = 'build' | 'development' | 'peer' | 'production' | 'root'; @@ -36,6 +36,21 @@ export interface DependencyConfig { export type ProjectDependsOn = string | DependencyConfig; +/** Configures aspects of the Docker scaffolding process. */ +export interface ProjectDockerScaffoldConfig { + /** + * List of glob patterns, relative from the project root, + * to include (or exclude) in the sources skeleton. + */ + include: string[]; +} + +/** Configures our Docker integration. */ +export interface ProjectDockerConfig { + /** Configures aspects of the Docker scaffolding process. */ + scaffold: ProjectDockerScaffoldConfig; +} + /** Supported programming languages that each project can be written in. */ export type LanguageType = | 'bash' @@ -180,6 +195,8 @@ export interface ProjectConfig { $schema?: string; /** Other projects that this project depends on. */ dependsOn: ProjectDependsOn[]; + /** Configures Docker integration for this project. */ + docker: ProjectDockerConfig; /** * A mapping of environment variables that will be set for * all tasks within the project. @@ -262,6 +279,21 @@ export interface PartialDependencyConfig { export type PartialProjectDependsOn = string | PartialDependencyConfig; +/** Configures aspects of the Docker scaffolding process. */ +export interface PartialProjectDockerScaffoldConfig { + /** + * List of glob patterns, relative from the project root, + * to include (or exclude) in the sources skeleton. + */ + include?: string[] | null; +} + +/** Configures our Docker integration. */ +export interface PartialProjectDockerConfig { + /** Configures aspects of the Docker scaffolding process. */ + scaffold?: PartialProjectDockerScaffoldConfig | null; +} + export type PartialOwnersPaths = string[] | Record; /** @@ -379,6 +411,8 @@ export interface PartialProjectConfig { $schema?: string | null; /** Other projects that this project depends on. */ dependsOn?: PartialProjectDependsOn[] | null; + /** Configures Docker integration for this project. */ + docker?: PartialProjectDockerConfig | null; /** * A mapping of environment variables that will be set for * all tasks within the project. diff --git a/packages/types/src/tasks-config.ts b/packages/types/src/tasks-config.ts index c667d361369..ca2a4143169 100644 --- a/packages/types/src/tasks-config.ts +++ b/packages/types/src/tasks-config.ts @@ -29,10 +29,19 @@ export type TaskMergeStrategy = 'append' | 'prepend' | 'replace'; export type TaskOutputStyle = 'buffer' | 'buffer-only-failure' | 'hash' | 'none' | 'stream'; /** A list of available shells on Unix. */ -export type TaskUnixShell = 'bash' | 'elvish' | 'fish' | 'ion' | 'nu' | 'pwsh' | 'xonsh' | 'zsh'; +export type TaskUnixShell = + | 'bash' + | 'elvish' + | 'fish' + | 'ion' + | 'murex' + | 'nu' + | 'pwsh' + | 'xonsh' + | 'zsh'; /** A list of available shells on Windows. */ -export type TaskWindowsShell = 'bash' | 'elvish' | 'fish' | 'nu' | 'pwsh' | 'xonsh'; +export type TaskWindowsShell = 'bash' | 'elvish' | 'fish' | 'murex' | 'nu' | 'pwsh' | 'xonsh'; /** Options to control task inheritance and execution. */ export interface TaskOptionsConfig { diff --git a/packages/types/src/workspace-config.ts b/packages/types/src/workspace-config.ts index a27f10f214b..1195a667b57 100644 --- a/packages/types/src/workspace-config.ts +++ b/packages/types/src/workspace-config.ts @@ -43,6 +43,48 @@ export interface ConstraintsConfig { tagRelationships: Record; } +/** Configures aspects of the Docker pruning process. */ +export interface DockerPruneConfig { + /** + * Automatically delete vendor directories (package manager + * dependencies, build targets, etc) while pruning. + * + * @default true + */ + deleteVendorDirectories?: boolean; + /** + * Automatically install production dependencies for all required + * toolchain's of the focused projects within the Docker build. + * + * @default true + */ + installToolchainDeps?: boolean; +} + +/** Configures aspects of the Docker scaffolding process. */ +export interface DockerScaffoldConfig { + /** + * Copy toolchain specific configs/manifests/files into + * the workspace skeleton. + * + * @default true + */ + copyToolchainFiles?: boolean; + /** + * List of glob patterns, relative from the workspace root, + * to include (or exclude) in the workspace skeleton. + */ + include: string[]; +} + +/** Configures our Docker integration. */ +export interface DockerConfig { + /** Configures aspects of the Docker pruning process. */ + prune: DockerPruneConfig; + /** Configures aspects of the Docker scaffolding process. */ + scaffold: DockerScaffoldConfig; +} + /** Configures experiments across the entire moon workspace. */ export interface ExperimentsConfig { /** Enables the new & modern action pipeline. */ @@ -226,6 +268,8 @@ export interface WorkspaceConfig { codeowners: CodeownersConfig; /** Configures boundaries and constraints between projects. */ constraints: ConstraintsConfig; + /** Configures Docker integration for the workspace. */ + docker: DockerConfig; /** Configures experiments across the entire moon workspace. */ experiments: ExperimentsConfig; /** @@ -297,6 +341,48 @@ export interface PartialConstraintsConfig { tagRelationships?: Record | null; } +/** Configures aspects of the Docker pruning process. */ +export interface PartialDockerPruneConfig { + /** + * Automatically delete vendor directories (package manager + * dependencies, build targets, etc) while pruning. + * + * @default true + */ + deleteVendorDirectories?: boolean | null; + /** + * Automatically install production dependencies for all required + * toolchain's of the focused projects within the Docker build. + * + * @default true + */ + installToolchainDeps?: boolean | null; +} + +/** Configures aspects of the Docker scaffolding process. */ +export interface PartialDockerScaffoldConfig { + /** + * Copy toolchain specific configs/manifests/files into + * the workspace skeleton. + * + * @default true + */ + copyToolchainFiles?: boolean | null; + /** + * List of glob patterns, relative from the workspace root, + * to include (or exclude) in the workspace skeleton. + */ + include?: string[] | null; +} + +/** Configures our Docker integration. */ +export interface PartialDockerConfig { + /** Configures aspects of the Docker pruning process. */ + prune?: PartialDockerPruneConfig | null; + /** Configures aspects of the Docker scaffolding process. */ + scaffold?: PartialDockerScaffoldConfig | null; +} + /** Configures experiments across the entire moon workspace. */ export interface PartialExperimentsConfig { /** Enables the new & modern action pipeline. */ @@ -468,6 +554,8 @@ export interface PartialWorkspaceConfig { codeowners?: PartialCodeownersConfig | null; /** Configures boundaries and constraints between projects. */ constraints?: PartialConstraintsConfig | null; + /** Configures Docker integration for the workspace. */ + docker?: PartialDockerConfig | null; /** Configures experiments across the entire moon workspace. */ experiments?: PartialExperimentsConfig | null; /** diff --git a/website/blog/2024-07-07_proto-v0.38.mdx b/website/blog/2024-07-07_proto-v0.38.mdx index 21778e107f2..90c866efef9 100644 --- a/website/blog/2024-07-07_proto-v0.38.mdx +++ b/website/blog/2024-07-07_proto-v0.38.mdx @@ -12,17 +12,24 @@ In this release, we're introducing a long requested feature, shell hooks! ## New experimental shell activation workflow -You've most likely used another version manager before proto, and may have used a workflow where `PATH` was automatically updated with versioned binaries of specific tools, or environment variables were injected into your shell. This functionality is what's known as shell hooks, and proto now has experimental support for them through a feature known as [shell activation](/docs/proto/workflows#shell-activation)! +You've most likely used another version manager before proto, and may have used a workflow where +`PATH` was automatically updated with versioned binaries of specific tools, or environment variables +were injected into your shell. This functionality is what's known as shell hooks, and proto now has +experimental support for them through a feature known as +[shell activation](/docs/proto/workflows#shell-activation)! ### How it works -For example, say you're using Zsh as your shell. You could now append the following expression at the bottom of your shell profile, which evaluates our new [`proto activate`](/docs/proto/commands/activate) command. +For example, say you're using Zsh as your shell. You could now append the following expression at +the bottom of your shell profile, which evaluates our new +[`proto activate`](/docs/proto/commands/activate) command. ```shell eval "$(proto activate zsh)" ``` -When the current working directory changes (via `cd`), or the CLI prompt changes, this activation workflow will trigger the following: +When the current working directory changes (via `cd`), or the CLI prompt changes, this activation +workflow will trigger the following: - Download and install necessary proto plugins (if they do not exist) - Load and resolve all `.prototools` configurations up the file system @@ -30,14 +37,20 @@ When the current working directory changes (via `cd`), or the CLI prompt changes - Export environment variables defined in `[env]` and `[tools.*.env]` - Prepend `PATH` with binary directories for detected tools -Pretty awesome right? We think so. But as mentioned above, this feature is highly experimental, and may not work properly across all shells (we're unable to test everything). So if you run into an issue, please report it! +Pretty awesome right? We think so. But as mentioned above, this feature is highly experimental, and +may not work properly across all shells (we're unable to test everything). So if you run into an +issue, please report it! ### Unlocked features -This new workflow unlocks some functionality that was previously not possible with proto shims/bins directly, and they are: +This new workflow unlocks some functionality that was previously not possible with proto shims/bins +directly, and they are: -- Directory paths to globally installed packages are now automatically prepended to `PATH`. Previously, you would need to add them manually. This was non-trivial if they were installed to versioned locations. -- Directory paths to pre-installed binaries within a tool are also prepended to `PATH`. For example, Rust/Cargo and Python provide a lot of executables that were ignored by our shims. +- Directory paths to globally installed packages are now automatically prepended to `PATH`. + Previously, you would need to add them manually. This was non-trivial if they were installed to + versioned locations. +- Directory paths to pre-installed binaries within a tool are also prepended to `PATH`. For example, + Rust/Cargo and Python provide a lot of executables that were ignored by our shims. - This workflow is 1 step closer to replacing [direnv](https://direnv.net/) itself. ## Other changes diff --git a/website/docs/config/project.mdx b/website/docs/config/project.mdx index a304c851247..a219815bffd 100644 --- a/website/docs/config/project.mdx +++ b/website/docs/config/project.mdx @@ -318,6 +318,36 @@ The project type is used in [task inheritance](../concepts/task-inheritance), ::: +## Integrations + +## `docker` + + + +Configures Docker integration for the current project. + +### `scaffold` + + + +Configures aspects of the Docker scaffolding process when +[`moon docker scaffold`](../commands/docker/scaffold) is executed. Only applies to the +[sources skeleton](../commands/docker/scaffold#sources). + +#### `include` + + + +List of globs in which to copy project-relative files into the `.moon/docker/sources` skeleton. When +not defined, defaults to `**/*`. + +```yaml title="moon.yml" {3,4} +docker: + scaffold: + include: + - 'src/**/*' +``` + ## Tasks ## `env` diff --git a/website/docs/config/workspace.mdx b/website/docs/config/workspace.mdx index d96f4567066..4e7dfdb8391 100644 --- a/website/docs/config/workspace.mdx +++ b/website/docs/config/workspace.mdx @@ -213,6 +213,88 @@ dependsOn: ['components'] tags: ['react'] ``` +## `docker` + + + +Configures Docker integration for the entire workspace. + +### `prune` + + + +Configures aspects of the Docker pruning process when +[`moon docker prune`](../commands/docker/prune) is executed. + +#### `deleteVendorDirectories` + + + +Automatically delete vendor directories (package manager dependencies, build targets, etc) while +pruning. For example, `node_modules` for JavaScript, or `target` for Rust. Defaults to `true`. + +```yaml title=".moon/workspace.yml" {3} +docker: + prune: + deleteVendorDirectories: false +``` + +> This process happens before toolchain dependencies are installed. + +#### `installToolchainDeps` + + + +Automatically install production dependencies for all required toolchain's of the focused projects +within the Docker build. For example, `node_modules` for JavaScript. Defaults to `true`. + +```yaml title=".moon/workspace.yml" {3} +docker: + prune: + installToolchainDeps: false +``` + +> This process happens after vendor directories are deleted. + +### `scaffold` + + + +Configures aspects of the Docker scaffolding process when +[`moon docker scaffold`](../commands/docker/scaffold) is executed. Only applies to the +[workspace skeleton](../commands/docker/scaffold#workspace). + +#### `copyToolchainFiles` + + + +Copy all toolchain specific configs, manifests, and related files in the entire repository into the +`.moon/docker/workspace` skeleton. This is required for certain tools, like package managers, to +work correctly. Defaults to `true`. + +```yaml title=".moon/workspace.yml" {3} +docker: + scaffold: + copyToolchainFiles: false +``` + +> If you disable this feature, you'll most likely need to manually `COPY` all necessary files in +> your `Dockerfile`. + +#### `include` + + + +List of globs in which to copy additional workspace-relative files into the `.moon/docker/workspace` +skeleton. When not defined, does nothing. + +```yaml title=".moon/workspace.yml" {3,4} +docker: + scaffold: + include: + - '**/package.json' +``` + ## `experiments` diff --git a/website/docs/guides/docker.mdx b/website/docs/guides/docker.mdx index 3a3ef2715a6..b29afad102f 100644 --- a/website/docs/guides/docker.mdx +++ b/website/docs/guides/docker.mdx @@ -225,8 +225,9 @@ RUN moon run : :::info -If you need additional files for your commands to run successfully, you can manually use `COPY` or -pass `--include` to the scaffold command. +If you need to copy additional files for your commands to run successfully, you can configure the +`docker.scaffold.include` setting in [`.moon/workspace.yaml`](../config/workspace#scaffold) (entire +workspace) or [`moon.yml`](../config/project#scaffold) (per project). ::: @@ -241,10 +242,17 @@ _must be ran_ within the context of a `Dockerfile`! RUN moon docker prune ``` -When ran, this command will do the following: +When ran, this command will do the following, in order: -- Install production only dependencies for the projects that were scaffolded. - Remove extraneous dependencies (`node_modules`) for unfocused projects. +- Install production only dependencies for the projects that were scaffolded. + +:::info + +This process can be customized using the `docker.prune` setting in +[`.moon/workspace.yaml`](../config/workspace#prune). + +::: ### Final result diff --git a/website/static/schemas/project.json b/website/static/schemas/project.json index 26bc784457a..54f9c7ef2b8 100644 --- a/website/static/schemas/project.json +++ b/website/static/schemas/project.json @@ -16,6 +16,15 @@ "$ref": "#/definitions/ProjectDependsOn" } }, + "docker": { + "title": "docker", + "description": "Configures Docker integration for this project.", + "allOf": [ + { + "$ref": "#/definitions/ProjectDockerConfig" + } + ] + }, "env": { "title": "env", "description": "A mapping of environment variables that will be set for all tasks within the project.", @@ -377,6 +386,37 @@ } ] }, + "ProjectDockerConfig": { + "description": "Configures our Docker integration.", + "type": "object", + "properties": { + "scaffold": { + "title": "scaffold", + "description": "Configures aspects of the Docker scaffolding process.", + "allOf": [ + { + "$ref": "#/definitions/ProjectDockerScaffoldConfig" + } + ] + } + }, + "additionalProperties": false + }, + "ProjectDockerScaffoldConfig": { + "description": "Configures aspects of the Docker scaffolding process.", + "type": "object", + "properties": { + "include": { + "title": "include", + "description": "List of glob patterns, relative from the project root, to include (or exclude) in the sources skeleton.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, "ProjectMetadataConfig": { "description": "Expanded information about the project.", "type": "object", @@ -1274,6 +1314,7 @@ "elvish", "fish", "ion", + "murex", "nu", "pwsh", "xonsh", @@ -1287,6 +1328,7 @@ "bash", "elvish", "fish", + "murex", "nu", "pwsh", "xonsh" diff --git a/website/static/schemas/tasks.json b/website/static/schemas/tasks.json index 920d85f5867..acca14e653c 100644 --- a/website/static/schemas/tasks.json +++ b/website/static/schemas/tasks.json @@ -674,6 +674,7 @@ "elvish", "fish", "ion", + "murex", "nu", "pwsh", "xonsh", @@ -687,6 +688,7 @@ "bash", "elvish", "fish", + "murex", "nu", "pwsh", "xonsh" diff --git a/website/static/schemas/workspace.json b/website/static/schemas/workspace.json index 7dcc2afce89..9991ba593c4 100644 --- a/website/static/schemas/workspace.json +++ b/website/static/schemas/workspace.json @@ -27,6 +27,15 @@ } ] }, + "docker": { + "title": "docker", + "description": "Configures Docker integration for the workspace.", + "allOf": [ + { + "$ref": "#/definitions/DockerConfig" + } + ] + }, "experiments": { "title": "experiments", "description": "Configures experiments across the entire moon workspace.", @@ -211,6 +220,71 @@ }, "additionalProperties": false }, + "DockerConfig": { + "description": "Configures our Docker integration.", + "type": "object", + "properties": { + "prune": { + "title": "prune", + "description": "Configures aspects of the Docker pruning process.", + "allOf": [ + { + "$ref": "#/definitions/DockerPruneConfig" + } + ] + }, + "scaffold": { + "title": "scaffold", + "description": "Configures aspects of the Docker scaffolding process.", + "allOf": [ + { + "$ref": "#/definitions/DockerScaffoldConfig" + } + ] + } + }, + "additionalProperties": false + }, + "DockerPruneConfig": { + "description": "Configures aspects of the Docker pruning process.", + "type": "object", + "properties": { + "deleteVendorDirectories": { + "title": "deleteVendorDirectories", + "description": "Automatically delete vendor directories (package manager dependencies, build targets, etc) while pruning.", + "default": true, + "type": "boolean" + }, + "installToolchainDeps": { + "title": "installToolchainDeps", + "description": "Automatically install production dependencies for all required toolchain's of the focused projects within the Docker build.", + "default": true, + "type": "boolean" + } + }, + "additionalProperties": false + }, + "DockerScaffoldConfig": { + "description": "Configures aspects of the Docker scaffolding process.", + "type": "object", + "properties": { + "copyToolchainFiles": { + "title": "copyToolchainFiles", + "description": "Copy toolchain specific configs/manifests/files into the workspace skeleton.", + "default": true, + "type": "boolean" + }, + "include": { + "title": "include", + "description": "List of glob patterns, relative from the workspace root, to include (or exclude) in the workspace skeleton.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, "ExperimentsConfig": { "description": "Configures experiments across the entire moon workspace.", "type": "object",