diff --git a/.eslintignore b/.eslintignore index 176d671c9fe..5f839f1a291 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,4 @@ -node_modules/ +.docusaurus/ build/ cjs/ coverage/ @@ -7,6 +7,7 @@ dts/ esm/ lib/ mjs/ +node_modules/ umd/ *.min.js *.map diff --git a/.eslintrc.js b/.eslintrc.js index 2df220a67bb..fed25813b43 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,6 +6,12 @@ module.exports = { tsconfigRootDir: __dirname, }, overrides: [ + { + files: ['**/*.config.js'], + rules: { + 'import/no-commonjs': 'off', + }, + }, { files: ['scripts/**/*'], rules: { @@ -14,5 +20,12 @@ module.exports = { 'promise/prefer-await-to-callbacks': 'off', }, }, + { + files: ['website/**/*'], + rules: { + // Path aliases + 'import/no-unresolved': 'off', + }, + }, ], }; diff --git a/.github/workflows/moon.yml b/.github/workflows/moon.yml index c96d6e5f4fd..30702ad29ba 100644 --- a/.github/workflows/moon.yml +++ b/.github/workflows/moon.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest] - node-version: ['14.19.0', '16.14.0', '18.0.0'] + node-version: [14, 16, 18] steps: - uses: actions/checkout@v3 with: @@ -45,10 +45,12 @@ jobs: with: toolchain: 1.60.0 profile: minimal + - uses: moonrepo/tool-version-action@v1 + with: + node: ${{ matrix.node-version }} - uses: actions-rs/cargo@v1 with: command: run args: -- --logLevel trace ci env: CLICOLOR_FORCE: true - MOON_NODE_VERSION: ${{ matrix.node-version }} diff --git a/.moon/project.yml b/.moon/project.yml index a962e222b97..8dab1087948 100644 --- a/.moon/project.yml +++ b/.moon/project.yml @@ -12,20 +12,14 @@ fileGroups: tasks: build: command: 'packemon' - args: - - 'build' - - '--addEngines' - - '--addExports' + args: 'build' inputs: - '@globs(sources)' - 'package.json' - 'tsconfig.json' - '/tsconfig.options.json' outputs: - - 'dts' - 'mjs' - env: - NODE_ENV: production format: command: 'prettier' @@ -51,21 +45,20 @@ tasks: - '.eslintcache' - '--color' - '--ext' - - '.ts,.tsx' + - '.js,.ts,.tsx' - '--fix' - '--ignore-path' - - '@in(4)' + - '@in(5)' - '--exit-on-fatal-error' - '--no-error-on-unmatched-pattern' - '--report-unused-disable-directives' - '.' - deps: - - 'runtime:typecheck' inputs: - '@globs(sources)' - '@globs(tests)' - 'package.json' - 'tsconfig.json' + - '*.js' - '/.eslintignore' - '/.eslintrc.js' - '/package.json' @@ -79,13 +72,12 @@ tasks: - '--preset' - 'jest-preset-beemo' - '--passWithNoTests' + - '.' inputs: - '@globs(sources)' - '@globs(tests)' - 'package.json' - 'jest.config.js' - outputs: - - 'dts' typecheck: command: 'tsc' diff --git a/.moon/workspace.yml b/.moon/workspace.yml index a81179d2118..863e692f9fe 100644 --- a/.moon/workspace.yml +++ b/.moon/workspace.yml @@ -9,3 +9,4 @@ node: projects: runtime: 'packages/runtime' + website: 'website' diff --git a/.prettierignore b/.prettierignore index eb76cd045be..25c3cfcd185 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,7 @@ coverage/ # Build folders +.docusaurus/ coverage/ build/ cjs/ @@ -8,6 +9,7 @@ dts/ esm/ lib/ mjs/ +node_modules/ umd/ # Node.js formats differently diff --git a/.yarn/versions/5ecc0c78.yml b/.yarn/versions/5ecc0c78.yml new file mode 100644 index 00000000000..572478968b0 --- /dev/null +++ b/.yarn/versions/5ecc0c78.yml @@ -0,0 +1,7 @@ +declined: + - "@moonrepo/cli" + - "@moonrepo/core-linux-x64-gnu" + - "@moonrepo/core-linux-x64-musl" + - "@moonrepo/core-macos-x64" + - "@moonrepo/core-windows-x64-msvc" + - "@moonrepo/runtime" diff --git a/.yarnrc.yml b/.yarnrc.yml index 820319505a2..b0434276c93 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -4,6 +4,7 @@ changesetIgnorePatterns: - '**/mjs/**/*' - '**/*.test.{js,ts,tsx}' - '**/*.{md,mdx}' + - 'website/**/*' enableGlobalCache: true @@ -11,6 +12,11 @@ enableTelemetry: false nodeLinker: node-modules +npmScopes: + fortawesome: + npmRegistryServer: https://npm.fontawesome.com + npmAuthToken: 23F99634-A6B0-4362-BB2B-7163253D741D + plugins: - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs spec: '@yarnpkg/plugin-interactive-tools' diff --git a/README.md b/README.md index 37818717518..8d53baf3ff1 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ -# Moon +# moon > Currently a work in progress! -Moon is a *m*onorepo *o*rganization, *o*rchestration, and *n*otification tool for JavaScript based -projects, written in Rust. Many of the concepts within Moon are heavily inspired from Bazel. +moon is a repository *m*anagement, *o*rganization, *o*rchestration, and *n*otification tool for +JavaScript based projects, written in Rust. Many of the concepts within moon are heavily inspired +from Bazel and other popular build systems, but tailored for the JavaScript ecosystem. - [Documentation](./docs/README.md) diff --git a/docs/roadmap.md b/ROADMAP.md similarity index 99% rename from docs/roadmap.md rename to ROADMAP.md index 4a1dad807d1..0130d0ca19e 100644 --- a/docs/roadmap.md +++ b/ROADMAP.md @@ -116,3 +116,7 @@ - [ ] `run-many` - [ ] `graph` - [ ] Spin up an interactive website with full project/task data + +# 0.3.0 + +- new website diff --git a/crates/cli/src/app.rs b/crates/cli/src/app.rs index 806323de520..6a5e8304e10 100644 --- a/crates/cli/src/app.rs +++ b/crates/cli/src/app.rs @@ -109,20 +109,19 @@ pub enum Commands { #[clap(help = "Target (project:task) to run")] target: TargetID, - // Affected #[clap( long, - help = "Only run target if affected by changed files", - help_heading = HEADING_AFFECTED + help = "Run dependents of the same task, as well as dependencies" )] - affected: bool, + dependents: bool, + // Affected #[clap( long, - help = "Determine affected from local changes instead of comparing against a base", + help = "Only run target if affected by changed files", help_heading = HEADING_AFFECTED )] - local: bool, + affected: bool, #[clap( arg_enum, @@ -133,6 +132,13 @@ pub enum Commands { )] status: RunStatus, + #[clap( + long, + help = "Determine affected against upstream by comparing against a base revision", + help_heading = HEADING_AFFECTED + )] + upstream: bool, + // Passthrough args (after --) #[clap( last = true, diff --git a/crates/cli/src/commands/ci.rs b/crates/cli/src/commands/ci.rs index 77b84038b1f..09ef6ee54c3 100644 --- a/crates/cli/src/commands/ci.rs +++ b/crates/cli/src/commands/ci.rs @@ -113,7 +113,7 @@ fn gather_runnable_targets( } else { debug!( target: TARGET, - "Not running target {} because it either has no `outputs` or `runInCi` is false", + "Not running target {} because it either has no `outputs` or `runInCI` is false", color::target(&target.id), ); } diff --git a/crates/cli/src/commands/run.rs b/crates/cli/src/commands/run.rs index 1b1e5f41202..bb3a02adf42 100644 --- a/crates/cli/src/commands/run.rs +++ b/crates/cli/src/commands/run.rs @@ -30,23 +30,24 @@ impl Default for RunStatus { pub struct RunOptions { pub affected: bool, - pub local: bool, + pub dependents: bool, pub status: RunStatus, pub passthrough: Vec, + pub upstream: bool, } async fn get_touched_files( workspace: &Workspace, status: &RunStatus, - local: bool, + upstream: bool, ) -> Result { let vcs = workspace.detect_vcs(); - let touched_files = if local { - vcs.get_touched_files().await? - } else { + let touched_files = if upstream { vcs.get_touched_files_between_revisions(vcs.get_default_branch(), "HEAD") .await? + } else { + vcs.get_touched_files().await? }; let files = match status { @@ -161,7 +162,8 @@ pub async fn run(target_id: &str, options: RunOptions) -> Result<(), Box Result<(), Box { run( target, RunOptions { affected: *affected, - local: *local, + dependents: *dependents, status: status.clone(), passthrough: passthrough.clone(), + upstream: *upstream, }, ) .await diff --git a/crates/cli/tests/run_test.rs b/crates/cli/tests/run_test.rs index 3ac7e576a5d..d50c7d9f368 100644 --- a/crates/cli/tests/run_test.rs +++ b/crates/cli/tests/run_test.rs @@ -122,7 +122,7 @@ mod caching { assert_eq!(state.item.target, "node:standard"); assert_eq!( state.item.hash, - "0957f07cd32663feeac762e10189d4be02c100309ed3e28c9d7491f1e040960d" + "3270284f4824b530c3006108757e715f73a43f949c811db7c0859aded12d9036" ); } } diff --git a/crates/config/src/main.rs b/crates/config/src/main.rs index 482acba46af..75948111abd 100644 --- a/crates/config/src/main.rs +++ b/crates/config/src/main.rs @@ -9,19 +9,19 @@ fn main() { let workspace_schema = schema_for!(WorkspaceConfig); fs::write( - "schemas/project.json", + "website/static/schemas/project.json", serde_json::to_string_pretty(&project_schema).unwrap(), ) .unwrap(); fs::write( - "schemas/global-project.json", + "website/static/schemas/global-project.json", serde_json::to_string_pretty(&global_project_schema).unwrap(), ) .unwrap(); fs::write( - "schemas/workspace.json", + "website/static/schemas/workspace.json", serde_json::to_string_pretty(&workspace_schema).unwrap(), ) .unwrap(); diff --git a/crates/config/src/project/global.rs b/crates/config/src/project/global.rs index 0a89cef6430..0332beeeefe 100644 --- a/crates/config/src/project/global.rs +++ b/crates/config/src/project/global.rs @@ -36,7 +36,7 @@ fn validate_tasks(map: &HashMap) -> Result<(), ValidationErr return Err(create_validation_error( "required_command", &format!("tasks.{}.command", name), - String::from("An npm/shell command is required."), + String::from("An npm/system command is required."), )); } } @@ -244,7 +244,7 @@ tasks: #[test] #[should_panic( - expected = "Invalid field `tasks.test.command`. An npm/shell command is required." + expected = "Invalid field `tasks.test.command`. An npm/system command is required." )] fn invalid_value_empty_field() { figment::Jail::expect_with(|jail| { diff --git a/crates/config/src/project/mod.rs b/crates/config/src/project/mod.rs index eaf64e2d186..6f413d34f1f 100644 --- a/crates/config/src/project/mod.rs +++ b/crates/config/src/project/mod.rs @@ -38,7 +38,7 @@ fn validate_tasks(map: &HashMap) -> Result<(), ValidationErr return Err(create_validation_error( "required_command", &format!("tasks.{}.command", name), - String::from("An npm/shell command is required."), + String::from("An npm/system command is required."), )); } } @@ -363,7 +363,7 @@ tasks: #[test] #[should_panic( - expected = "Invalid field `tasks.test.command`. An npm/shell command is required." + expected = "Invalid field `tasks.test.command`. An npm/system command is required." )] fn invalid_value_empty_field() { figment::Jail::expect_with(|jail| { diff --git a/crates/config/src/project/task.rs b/crates/config/src/project/task.rs index cb706fdcf1b..058e8c83dfe 100644 --- a/crates/config/src/project/task.rs +++ b/crates/config/src/project/task.rs @@ -78,6 +78,7 @@ pub struct TaskOptionsConfig { pub retry_count: Option, + #[serde(rename = "runInCI")] pub run_in_ci: Option, pub run_from_workspace_root: Option, diff --git a/crates/config/src/workspace/node.rs b/crates/config/src/workspace/node.rs index 1f6bfaddb90..840217647ea 100644 --- a/crates/config/src/workspace/node.rs +++ b/crates/config/src/workspace/node.rs @@ -144,6 +144,7 @@ pub struct NodeConfig { pub sync_version_manager_config: Option, + #[serde(default = "default_node_version")] #[validate(custom = "validate_node_version")] pub version: String, diff --git a/crates/config/templates/global_project.yml b/crates/config/templates/global_project.yml index 0f0678976a9..4656e5e2c51 100644 --- a/crates/config/templates/global_project.yml +++ b/crates/config/templates/global_project.yml @@ -35,7 +35,7 @@ fileGroups: - '**/*.mdx' # OPTIONAL: A task is an action that is ran within the context of a project, and wraps -# around an npm or shell command. Tasks that are defined here and inherited by all projects +# around an npm or system command. Tasks that are defined here and inherited by all projects # within the workspace, but can be overridden per project. # # This setting requires a map, where the key is a unique name for the task, and the value is an diff --git a/crates/config/templates/project.yml b/crates/config/templates/project.yml index 217a51b905b..5e1f03a198d 100644 --- a/crates/config/templates/project.yml +++ b/crates/config/templates/project.yml @@ -38,5 +38,5 @@ dependsOn: [] fileGroups: {} # OPTIONAL: A task is an action that is ran within the context of a project, and -# wraps around an npm or shell command. View `.moon/project.yml` for more info. +# wraps around an npm or system command. View `.moon/project.yml` for more info. tasks: {} diff --git a/crates/config/templates/workspace.yml b/crates/config/templates/workspace.yml index 0aac5fc8f73..d59340d693c 100644 --- a/crates/config/templates/workspace.yml +++ b/crates/config/templates/workspace.yml @@ -7,7 +7,7 @@ $schema: 'https://moonrepo.dev/schemas/workspace.json' projects: example: 'packages/example' -# OPTIONAL: Configures Node.js within the toolchain. Moon manages its own version of Node.js +# OPTIONAL: Configures Node.js within the toolchain. moon manages its own version of Node.js # instead of relying on a version found on the host machine. This ensures deterministic # and reproducible builds across any machine. node: @@ -33,7 +33,7 @@ node: # Accepts "nodenv" (.node-version), "nvm" (.nvmrc), or none. # syncVersionManagerConfig: 'nvm' -# OPTIONAL: Configures how Moon integrates with TypeScript. +# OPTIONAL: Configures how moon integrates with TypeScript. typescript: # OPTIONAL: Name of `tsconfig.json` file in project root. projectConfigFileName: 'tsconfig.json' diff --git a/crates/project/src/task.rs b/crates/project/src/task.rs index bc81276b1e0..0d43f7c0759 100644 --- a/crates/project/src/task.rs +++ b/crates/project/src/task.rs @@ -7,7 +7,7 @@ use moon_config::{ FilePath, FilePathOrGlob, TargetID, TaskConfig, TaskMergeStrategy, TaskOptionsConfig, TaskType, }; use moon_logger::{color, debug, trace}; -use moon_utils::{fs, path}; +use moon_utils::{fs, path, string_vec}; use serde::{Deserialize, Serialize}; use std::collections::HashSet; @@ -100,13 +100,15 @@ impl Task { pub fn from_config(target: TargetID, config: &TaskConfig) -> Self { let cloned_config = config.clone(); let cloned_options = cloned_config.options; + let command = cloned_config.command.unwrap_or_default(); + let is_long_running = command == "serve" || command == "start"; let task = Task { args: cloned_config.args.unwrap_or_default(), - command: cloned_config.command.unwrap_or_default(), + command, deps: cloned_config.deps.unwrap_or_default(), env: cloned_config.env.unwrap_or_default(), - inputs: cloned_config.inputs.unwrap_or_default(), + inputs: cloned_config.inputs.unwrap_or_else(|| string_vec!["**/*"]), input_globs: vec![], input_paths: HashSet::new(), options: TaskOptions { @@ -116,7 +118,7 @@ impl Task { merge_inputs: cloned_options.merge_inputs.unwrap_or_default(), merge_outputs: cloned_options.merge_outputs.unwrap_or_default(), retry_count: cloned_options.retry_count.unwrap_or_default(), - run_in_ci: cloned_options.run_in_ci.unwrap_or_default(), + run_in_ci: cloned_options.run_in_ci.unwrap_or(!is_long_running), run_from_workspace_root: cloned_options.run_from_workspace_root.unwrap_or_default(), }, outputs: cloned_config.outputs.unwrap_or_default(), @@ -266,7 +268,7 @@ impl Task { for input in &token_resolver.resolve(&self.inputs, None)? { // We cant canonicalize here as these inputs may not exist! if path::is_path_glob(input) { - self.input_globs.push(path::normalize_glob(input)); + self.input_globs.push(path::normalize_glob(input)?); } else { self.input_paths.insert(path::normalize(input)); } @@ -304,11 +306,6 @@ impl Task { /// Return true if this task is affected, based on touched files. /// Will attempt to find any file that matches our list of inputs. pub fn is_affected(&self, touched_files: &TouchedFilePaths) -> Result { - // We have nothing to compare against, so treat it as always affected - if self.inputs.is_empty() { - return Ok(true); - } - trace!( target: &format!("moon:project:{}", self.target), "Checking if affected using input files: {}", @@ -338,7 +335,7 @@ impl Task { let mut affected = self.input_paths.contains(file); if !affected && has_globs { - affected = fs::matches_globset(&globset, file); + affected = fs::matches_globset(&globset, file)?; } trace!( @@ -478,15 +475,6 @@ mod tests { mod is_affected { use super::*; - #[test] - fn returns_true_if_empty_inputs() { - let workspace_root = get_fixtures_dir("base"); - let project_root = workspace_root.join("files-and-dirs"); - let task = create_expanded_task(&workspace_root, &project_root, None).unwrap(); - - assert!(task.is_affected(&HashSet::new()).unwrap()); - } - #[test] fn returns_true_if_matches_file() { let workspace_root = get_fixtures_dir("base"); diff --git a/crates/project/tests/project_test.rs b/crates/project/tests/project_test.rs index fa562bb829a..bc2d83d4e16 100644 --- a/crates/project/tests/project_test.rs +++ b/crates/project/tests/project_test.rs @@ -217,6 +217,7 @@ mod tasks { use moon_project::test::{ create_expanded_task as create_expanded_task_internal, create_file_groups_config, }; + use moon_utils::path; use moon_utils::test::wrap_glob; use pretty_assertions::assert_eq; @@ -303,6 +304,15 @@ mod tasks { ) .unwrap(); + let mut task = Task::from_config( + Target::format("id", "standard").unwrap(), + &mock_task_config("cmd"), + ); + + // Expanded + task.input_globs + .push(path::normalize_glob(&workspace_root.join("tasks/no-tasks/**/*")).unwrap()); + assert_eq!( project, Project { @@ -314,13 +324,7 @@ mod tasks { .unwrap(), file_groups: HashMap::new(), source: String::from("tasks/no-tasks"), - tasks: HashMap::from([( - String::from("standard"), - Task::from_config( - Target::format("id", "standard").unwrap(), - &mock_task_config("cmd") - ) - )]), + tasks: HashMap::from([(String::from("standard"), task)]), } ); } @@ -340,6 +344,39 @@ mod tasks { ) .unwrap(); + let mut build = Task::from_config( + Target::format("id", "build").unwrap(), + &mock_task_config("webpack"), + ); + + let mut std = Task::from_config( + Target::format("id", "standard").unwrap(), + &mock_task_config("cmd"), + ); + + let mut test = Task::from_config( + Target::format("id", "test").unwrap(), + &mock_task_config("jest"), + ); + + let mut lint = Task::from_config( + Target::format("id", "lint").unwrap(), + &mock_task_config("eslint"), + ); + + // Expanded + let wild_glob = workspace_root.join("tasks/basic/**/*"); + + build + .input_globs + .push(path::normalize_glob(&wild_glob).unwrap()); + std.input_globs + .push(path::normalize_glob(&wild_glob).unwrap()); + test.input_globs + .push(path::normalize_glob(&wild_glob).unwrap()); + lint.input_globs + .push(path::normalize_glob(&wild_glob).unwrap()); + assert_eq!( project, Project { @@ -356,34 +393,10 @@ mod tasks { file_groups: HashMap::new(), source: String::from("tasks/basic"), tasks: HashMap::from([ - ( - String::from("build"), - Task::from_config( - Target::format("id", "build").unwrap(), - &mock_task_config("webpack") - ) - ), - ( - String::from("standard"), - Task::from_config( - Target::format("id", "standard").unwrap(), - &mock_task_config("cmd") - ) - ), - ( - String::from("test"), - Task::from_config( - Target::format("id", "test").unwrap(), - &mock_task_config("jest") - ) - ), - ( - String::from("lint"), - Task::from_config( - Target::format("id", "lint").unwrap(), - &mock_task_config("eslint") - ) - ) + (String::from("build"), build), + (String::from("standard"), std), + (String::from("test"), test), + (String::from("lint"), lint) ]), } ); diff --git a/crates/utils/src/fs.rs b/crates/utils/src/fs.rs index b5073525ba1..a499e5f0d18 100644 --- a/crates/utils/src/fs.rs +++ b/crates/utils/src/fs.rs @@ -94,17 +94,17 @@ pub async fn link_dir(from_root: &Path, from: &Path, to_root: &Path) -> Result<( } #[cfg(not(windows))] -pub fn matches_globset(globset: &GlobSet, path: &Path) -> bool { - globset.is_match(path) +pub fn matches_globset(globset: &GlobSet, path: &Path) -> Result { + Ok(globset.is_match(path)) } // globset doesnt match against inputs that use backwards slashes // https://github.com/BurntSushi/ripgrep/issues/2001 #[cfg(windows)] -pub fn matches_globset(globset: &GlobSet, path: &Path) -> bool { +pub fn matches_globset(globset: &GlobSet, path: &Path) -> Result { use crate::path::normalize_glob; - globset.is_match(&PathBuf::from(normalize_glob(path))) + Ok(globset.is_match(&PathBuf::from(normalize_glob(path)?))) } pub async fn metadata(path: &Path) -> Result { diff --git a/crates/utils/src/path.rs b/crates/utils/src/path.rs index 4429ad94f64..79f2dc9fc51 100644 --- a/crates/utils/src/path.rs +++ b/crates/utils/src/path.rs @@ -64,16 +64,16 @@ pub fn normalize(path: &Path) -> PathBuf { path.to_path_buf().clean() } -pub fn normalize_glob(path: &Path) -> String { +pub fn normalize_glob(path: &Path) -> Result { // Always use forward slashes for globs - let glob = standardize_separators(&path.to_string_lossy()); + let glob = standardize_separators(&path_to_string(path)?); // Remove UNC prefix as it breaks glob matching if cfg!(windows) { - return glob.replace("//?/", ""); + return Ok(glob.replace("//?/", "")); } - glob + Ok(glob) } #[cfg(not(windows))] diff --git a/crates/utils/src/process.rs b/crates/utils/src/process.rs index aaec5acc052..77fa7e431ea 100644 --- a/crates/utils/src/process.rs +++ b/crates/utils/src/process.rs @@ -282,14 +282,7 @@ impl Command { let mut command_line = path::replace_home_dir(&format!("{} {}", self.bin, args.join(" "))); if input.is_some() { - command_line = format!( - "{} {} {}", - color::muted_light(&input.unwrap().replace('\n', " ")), - color::muted(">"), - color::shell(&command_line) - ); - } else { - command_line = color::shell(&command_line); + command_line = format!("{} > {}", input.unwrap().replace('\n', " "), command_line); } let mut envs_list = vec![]; @@ -315,7 +308,7 @@ impl Command { trace!( target: "moon:utils", "Running command {} (in {}){}", - command_line, + color::shell(&command_line), if let Some(cwd) = cmd.get_current_dir() { color::path(cwd) } else { diff --git a/crates/utils/src/test.rs b/crates/utils/src/test.rs index 63abe9f6725..de016385ddb 100644 --- a/crates/utils/src/test.rs +++ b/crates/utils/src/test.rs @@ -1,6 +1,8 @@ use crate::path; +use crate::process::output_to_string; use std::env; use std::path::{Path, PathBuf}; +use std::process::Command; pub fn create_fixtures_sandbox(dir: &str) -> assert_fs::fixture::TempDir { use assert_fs::prelude::*; @@ -11,6 +13,44 @@ pub fn create_fixtures_sandbox(dir: &str) -> assert_fs::fixture::TempDir { .copy_from(get_fixtures_dir(dir), &["**/*"]) .unwrap(); + // Initialize a git repo so that VCS commands work + Command::new("git") + .args(["init", "--initial-branch", "master"]) + .current_dir(temp_dir.path()) + .output() + .unwrap_or_else(|_| panic!("Failed to initialize git for fixtures sandbox: {}", dir)); + + // We must also add the files to the index + let out = Command::new("git") + .args(["add", "--all", "."]) + .current_dir(temp_dir.path()) + .output() + .unwrap_or_else(|_| { + panic!( + "Failed to add files to git index for fixtures sandbox: {}", + dir + ) + }); + + if !out.status.success() { + eprintln!("{}", output_to_string(&out.stderr)); + } + + // And commit them... this seems like a lot of overhead? + let out = Command::new("git") + .args(["commit", "-m", "'Fixtures'"]) + .env("GIT_AUTHOR_NAME", "moon tests") + .env("GIT_AUTHOR_EMAIL", "fakeemail@moonrepo.dev") + .env("GIT_COMMITTER_NAME", "moon tests") + .env("GIT_COMMITTER_EMAIL", "fakeemail@moonrepo.dev") + .current_dir(temp_dir.path()) + .output() + .unwrap_or_else(|_| panic!("Failed to commit files for fixtures sandbox: {}", dir)); + + if !out.status.success() { + eprintln!("{}", output_to_string(&out.stderr)); + } + temp_dir } @@ -36,7 +76,7 @@ pub fn replace_fixtures_dir(value: &str, dir: &Path) -> String { // We need to do this so slashes are accurate and always forward pub fn wrap_glob(path: &Path) -> PathBuf { - PathBuf::from(path::normalize_glob(path)) + PathBuf::from(path::normalize_glob(path).unwrap()) } pub fn create_moon_command(fixture: &str) -> assert_cmd::Command { diff --git a/crates/workspace/src/actions/hashing/target.rs b/crates/workspace/src/actions/hashing/target.rs index ca48fde6526..808e1574a8c 100644 --- a/crates/workspace/src/actions/hashing/target.rs +++ b/crates/workspace/src/actions/hashing/target.rs @@ -76,27 +76,26 @@ pub async fn create_target_hasher( let mut hashed_file_tree = vcs.get_file_tree_hashes(&project.source).await?; // Input globs are absolute paths, so we must do the same - hashed_file_tree.retain(|k, _| fs::matches_globset(&globset, &workspace.root.join(k))); + hashed_file_tree + .retain(|k, _| fs::matches_globset(&globset, &workspace.root.join(k)).unwrap()); hasher.hash_inputs(hashed_file_tree); } // Include local file changes so that development builds work. // Also run this LAST as it should take highest precedence! - if vcs.is_enabled() { - let local_files = vcs.get_touched_files().await?; - - if !local_files.all.is_empty() { - // Only hash files that are within the task's inputs - let files = local_files - .all - .into_iter() - .filter(|f| fs::matches_globset(&globset, &workspace.root.join(f))) - .collect::>(); - - if !files.is_empty() { - hasher.hash_inputs(vcs.get_file_hashes(&files).await?); - } + let local_files = vcs.get_touched_files().await?; + + if !local_files.all.is_empty() { + // Only hash files that are within the task's inputs + let files = local_files + .all + .into_iter() + .filter(|f| fs::matches_globset(&globset, &workspace.root.join(f)).unwrap()) + .collect::>(); + + if !files.is_empty() { + hasher.hash_inputs(vcs.get_file_hashes(&files).await?); } } diff --git a/crates/workspace/src/actions/install_node_deps.rs b/crates/workspace/src/actions/install_node_deps.rs index 947c996e2b6..88864d2606a 100644 --- a/crates/workspace/src/actions/install_node_deps.rs +++ b/crates/workspace/src/actions/install_node_deps.rs @@ -24,7 +24,8 @@ fn add_package_manager(workspace: &mut Workspace) -> bool { ), }; - if workspace.toolchain.get_node().is_corepack_aware() + if manager_version != "npm@inherit" + && workspace.toolchain.get_node().is_corepack_aware() && workspace.package_json.set_package_manager(&manager_version) { debug!( diff --git a/crates/workspace/src/actions/run_target.rs b/crates/workspace/src/actions/run_target.rs index b3beb66e101..3b8a7385815 100644 --- a/crates/workspace/src/actions/run_target.rs +++ b/crates/workspace/src/actions/run_target.rs @@ -230,6 +230,13 @@ pub async fn run_target( let hasher = create_target_hasher(&workspace, &project, task).await?; let hash = hasher.to_hash(); + debug!( + target: TARGET, + "Generated hash {} for target {}", + color::symbol(&hash), + color::id(target_id) + ); + if cache.item.hash == hash { print_target_label(target_id, "(cached)", cache.item.exit_code != 0); print_cache_item(&cache.item); diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 1955f234c3b..00000000000 --- a/docs/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Concepts - -- [Workspace](./workspace.md) -- [Toolchain](./toolchain.md) -- [Projects](./project.md) -- [Tasks](./task.md) - -# Guides - -- [CLI commands](./commands.md) diff --git a/docs/commands.md b/docs/commands.md deleted file mode 100644 index f8a0489e9c7..00000000000 --- a/docs/commands.md +++ /dev/null @@ -1,128 +0,0 @@ -# CLI commands - -- [Environment](#environment) - - [`bin`](#bin) - - [`setup`](#setup) - - [`teardown`](#teardown) -- [Projects](#projects) - - [`project`](#project) - - [`project-graph`](#project-graph) -- [Jobs](#jobs) - - [`ci`](#ci) - - [Revision comparison](#revision-comparison) - - [Parallelism](#parallelism) - -## Environment - -### `bin` - -The `bin ` command will return an absolute path to a tool's binary within the toolchain. If a -tool has not been configured or installed, this will return a 1 or 2 exit code respectively, with no -value. - -```shell -$ moon bin node -/Users/example/.moon/tools/node/x.x.x/bin/node -``` - -A tool is considered "not configured" when not in use, for example, querying yarn/pnpm when the -package manager is configured for "npm". A tool is considered "not installed", when it has not been -downloaded and installed into the tools directory. - -### `setup` - -The `setup` command can be used to setup the developer and pipeline environments. It achieves this -by doing the following: - -- Downloading and installing all configured tools into the toolchain. - -```shell -$ moon setup -``` - -> This command should rarely be used, as the environment is automatically setup when running other -> commands, like detecting affected projects, running a task, or generating a build artifact. - -### `teardown` - -The `teardown` command, as its name infers, will teardown and clean the current environment, -opposite the [`setup`](#setup) command. It achieves this by doing the following: - -- Uninstalling all configured tools in the toolchain. -- Removing any download or temporary files/folders. - -```shell -$ moon teardown -``` - -## Projects - -### `project` - -The `project ` command will display all available information about a project that has been -configured and exists within the graph. If a project does not exist, the program will return with a -1 exit code. - -```shell -$ moon project web -``` - -### `project-graph` - -The `project-graph` command will generate a graph of all configured projects, with edges between -dependencies, and will output the graph in -[Graphviz DOT format](https://graphviz.org/doc/info/lang.html). This output can then be used by any -tool or program that supports DOT, for example, this -[live preview visualizer](https://dreampuf.github.io/GraphvizOnline). - -```shell -$ moon project-graph > graph.dot -``` - -> A project ID can be passed to focus the graph to only that project and it's dependencies. For -> example, `moon project-graph web`. - -## Jobs - -### `ci` - -The `ci` command is a special command that should be ran in a continuous integration (CI) -environment, as it does all the heavy lifting necessary for effectively running jobs. It achieves -this by doing the following: - -- Determines touched files by comparing the current HEAD against the base. -- Determines all targets (project + tasks) that need to run based on touched files. -- Additionally runs affected targets dependencies _and_ dependents. -- Generates a task and dependency graph. -- Installs the toolchain, Node.js, and npm dependencies. -- Runs all tasks within the graph using a thread pool. -- Displays stats about all passing, failed, and invalid tasks. - -```shell -$ moon ci -``` - -#### Revision comparison - -By default the command will compare the current branches HEAD against the base revision, which is -typically the configured `vcs.defaultBranch` (master, main, trunk, etc). Both of these can be -customized with the `--base` and `--head` options respectively. - -```shell -$ moon ci --base another-branch --head -``` - -#### Parallelism - -If your CI environment supports splitting up tasks across multiple jobs, then you can utilize Moon's -built in parallelism, by passing `--jobTotal` and `--job` options. The `--jobTotal` option is an -integer of the total number of jobs available, and `--job` is the current index (0 based) amongst -the total. - -When these options are passed, Moon will only run affected targets based on the current job slice. - -```shell -$ moon ci --job 1 --jobTotal 5 -``` - -> Your CI environment may provide environment variables for these 2 values. diff --git a/docs/project.md b/docs/project.md deleted file mode 100644 index 9b3c3bd1c50..00000000000 --- a/docs/project.md +++ /dev/null @@ -1,249 +0,0 @@ -# Projects - -- [ID](#id) -- [Configuration](#configuration) - - [`project.yml`](#projectyml) - - [project](#project) - - [type](#type) - - [name](#name) - - [description](#description) - - [channel](#channel) - - [owner](#owner) - - [maintainers](#maintainers) - - [dependsOn](#dependson) - - [fileGroups](#filegroups) - - [tasks](#tasks) - - [workspace](#workspace) - - [inheritedTasks](#inheritedTasks) - - [exclude](#exclude) - - [include](#include) - - [rename](#rename) - - [`package.json`](#packagejson) - - [`tsconfig.json`](#tsconfigjson) - -A project is a library, application, package, binary, tool, etc, that contains source files, test -files, assets, resources, and more. A project must exist and be configured within a -[workspace](./workspace.md). - -## ID - -A project identifier, also knows an a PID, or simply ID, is a unique resource for locating a -project. The PID is explicitly configured within [`.moon/workspace.yml`](./workspace.md#projects), -as a key within the `projects` setting. - -PIDs are used heavily by configuration and the command line to link and reference everything. -They're also a much easier concept for remembering projects than file system paths, and they -typically can be written with less key strokes. - -## Configuration - -All project configuration is located at the root of the project folder. - -### `project.yml` - -This configuration file _is not required_ but can be used to define additional metadata for a -project in the graph. - -#### project - -The optional `project` setting defines metadata about the project itself. Although this setting is -optional, when defined, all fields within it _must_ be defined as well. - -```yaml -project: - type: 'library' - name: 'Moon' - description: 'A monorepo management tool.' - channel: '#moon' - owner: 'infra' - maintainers: ['miles.johnson'] -``` - -The information listed within `project` is purely informational and primarily displayed within the -CLI. However, this setting exists for you, your team, and your company, as a means to identify and -organize all projects. Feel free to build your own tooling around these settings! - -##### type - -The type of project. Supports the following values: - -- `application` - A backend or frontend application that communicates over HTTP, TCP, RPC, etc. -- `library` - A self-contained, shareable, and publishable set of code. -- `tool` - An internal tool, command line program, one-off script, etc. - -##### name - -A human readable name of the project. This is _different_ from the unique project ID. - -##### description - -A description of what the project does and aims to achieve. Be as descriptive as possible, as this -is the kind of information search engines would index on. - -##### channel - -The Slack, Discord, Teams, IRC, etc channel name (with leading #) in which to discuss the project. - -##### owner - -The team or organization that owns the project. Can be a title, LDAP name, GitHub team, etc. We -suggest _not_ listing people/developers as the owner, use [maintainers](#maintainers) instead. - -##### maintainers - -A list of people/developers that maintain the project, review code changes, and can provide support. -Can be a name, email, LDAP/GitHub username, etc, the choice is yours. - -#### dependsOn - -The optional `dependsOn` setting defines _other_ projects that _this_ project depends on, primarily -when generating the project and dependency graphs. The most common use case is building those -projects _before_ building this one. It will also sync [package.json](#packagejson) and -[tsconfig.json](#tsconfigjson) when applicable. - -When defined, this setting requires an array of project IDs, which are the keys found in the -[`projects`](./workspace.md#projects) map. - -```yaml -dependsOn: - - 'dsl' - - 'hooks' -``` - -#### fileGroups - -> Knowledge of [`.moon/project.yml`](./workspace.md#filegroups) is required before continuing. - -As mentioned in the link above, file groups are a mechanism for grouping similar types of files -within a project using file glob patterns. By default, this setting _is not required_ for the -following reasons: - -- File groups are an optional feature, and are designed for advanced use cases. -- File groups defined in `.moon/project.yml` will be inherited by all projects. - -The only scenario in which to define file groups at the project-level is when you want to _override_ -file groups defined at the workspace-level. - -For example, say we want to override the `sources` file group because our source folder is named -"lib" and not "src", we would define our `project.yml` as follows. - -```yaml -fileGroups: - sources: - - 'lib/**/*' - - 'types/**/*' -``` - -> File groups defined in `project.yml` will override file groups defined in `.moon/project.yml` by -> object key, and _will not_ merge the value arrays. - -#### tasks - -> Knowledge of [`.moon/project.yml`](./workspace.md#tasks) is required before continuing. - -As mentioned in the link above, [tasks](./task.md) are actions that are ran within the context of a -project, and commonly wrap an npm or shell command. By default, this setting _is not required_ as -tasks are typically defined globally, and not all projects require tasks. - -With that being said, projects can define tasks that are unique to themselves, and can also define -tasks that merge with global tasks of the same name! - -```yaml -tasks: - # Task unique to the project - package: - command: 'packemon' - args: - - 'build' - - '--addExports' - # Merge with a global task and provide additional args - lint: - args: - - '--cache' -``` - -> Multiple [strategies](./task.md#merge-strategies) exist when merging tasks, so choose the one -> that's best for you! - -#### workspace - -The optional `workspace` setting dictates how a project interacts with settings at the -workspace-level. - -##### inheritedTasks - -Provides a layer of control when inheriting tasks from the -[global project config](./workspace.md#projectyml). - -###### exclude - -The optional `exclude` setting permits a project to exclude specific tasks from being inherited. It -accepts a list of strings, where each string is the ID of a global task to exclude. - -```yaml -workspace: - inheritedTasks: - # Exclude the inherited `test` task for this project - exclude: ['test'] -``` - -> Exclusion is applied after inclusion and before renaming. - -###### include - -The optional `include` setting permits a project to _only_ include specific inherited tasks (works -like an allow/white list). It accepts a list of strings, where each string is the ID of a global -task to include. - -When this field is not defined, the project will inherit all tasks from the global project config. - -```yaml -workspace: - inheritedTasks: - # Include *no* tasks (works like a full exclude) - include: [] - - # Only include the `lint` and `test` tasks for this project - include: - - 'lint' - - 'test' -``` - -> Inclusion is applied before exclusion and renaming. - -###### rename - -The optional `rename` setting permits a project to rename the inherited task within the current -project. It accepts a map of strings, where the key is the original ID (found in the global project -config), and the value is the new ID to use. - -For example, say we have 2 tasks in the global project config called `buildPackage` and -`buildApplication`, but we only need 1, and since we're an application, we should omit and rename. - -```yaml -workspace: - inheritedTasks: - exclude: ['buildPackage'] - rename: - buildApplication: 'build' -``` - -> Renaming occurs after inclusion and exclusion. - -### `package.json` - -A Moon project _does not require_ a `package.json`, but when one exists, the following functionality -is enabled. - -- Dependency versions are included when computing cache keys. -- Depended on projects (`dependsOn`) are mapped as npm/pnpm/yarn workspace dependencies (when - applicable). - -### `tsconfig.json` - -A Moon project _does not require_ TypeScript or a `tsconfig.json`, but when one exists, the -following functionality is enabled. - -- Depended on projects (`dependsOn`) are mapped as TypeScript project references (when applicable). - -> File name can be customized with the `typescript.projectConfigFileName` setting. diff --git a/docs/task.md b/docs/task.md deleted file mode 100644 index 6c546d558d6..00000000000 --- a/docs/task.md +++ /dev/null @@ -1,408 +0,0 @@ -# Tasks - -- [Tokens](#tokens) - - [Functions](#functions) - - [File groups](#file-groups) - - [Inputs](#inputs) - - [Outputs](#outputs) - - [Variables](#variables) -- [Targets](#targets) -- [Merge strategies](#merge-strategies) - -Tasks are commands that are ran in the context of a [project](./project.md). Underneath the hood, a -task is simply a node module binary or a system/shell command that is ran as a child-process. Tasks -communicate between the Moon client and server through a JSON-like message system. - -## Tokens - -Tokens are variables and functions that can be used by `args`, `inputs`, and `outputs` when -configuring a task. They provide a way of accessing file group paths, referencing values from other -task fields, and referencing metadata about the project and task itself. - -### Functions - -A token function is labeled as such as it takes a single argument, starts with an `@`, and is -formatted as `@name(arg)`. The following token functions are available, grouped by their -functionality. - -> Token functions _must_ be the only content within a list item, as they expand to multiple file -> paths. - -#### File groups - -These functions reference file groups by name, where the name is passed as the argument. - -##### `@dirs` - -> Usable in `args` and `inputs`. - -The `@dirs(file_group)` token will be replaced with an expanded list of directory paths, derived -from the file group of the same name. If a glob pattern is detected within the file group, it will -walk the file system and aggregate all directories found. - -When used in `args`, it will return relative or absolute paths, depending on run context, while -`inputs` will return absolute paths. - -```yaml -fileGroups: - lintable: - - 'src' - - 'tests' - - 'scripts' - -# Configured as -tasks: - lint: - command: 'eslint' - args: - - '@dirs(lintable)' - - '--color' - inputs: - - '@dirs(lintable)' - -# Resolves to -tasks: - lint: - command: 'eslint' - args: - - 'src' - - 'tests' - - 'scripts' - - '--color' - inputs: - - '/path/to/project/src' - - '/path/to/project/tests' - - '/path/to/project/scripts' -``` - -##### `@files` - -> Usable in `args` and `inputs`. - -The `@files(file_group)` token will be replaced with an expanded list of file paths, derived from -the file group of the same name. If a glob pattern is detected within the file group, it will walk -the file system and aggregate all files found. - -When used in `args`, it will return relative or absolute paths, depending on run context, while -`inputs` will return absolute paths. - -```yaml -fileGroups: - config: - - '*.config.js' - - 'package.json' - -# Configured as -tasks: - build: - command: 'webpack' - args: - - 'build' - - '@files(config)' - inputs: - - '@files(config)' - -# Resolves to -tasks: - build: - command: 'webpack' - args: - - 'build' - - 'babel.config.js' - - 'webpack.babel.js' - - 'package.json' - inputs: - - '/path/to/project/babel.config.js' - - '/path/to/project/webpack.babel.js' - - '/path/to/project/package.json' -``` - -##### `@globs` - -> Usable in `args` and `inputs`. - -The `@globs(file_group)` token will be replaced with an expanded list of glob patterns (as-is), -derived from the file group of the same name. If a non-glob pattern is detected within the file -group, it will be ignored. - -When used in `args`, it will return relative or absolute paths, depending on run context, while -`inputs` will return absolute paths _and_ also be used in affected files detection by matching -against the patterns. - -```yaml -fileGroups: - tests: - - 'tests/**/*' - - '**/__tests__/**/*' - -# Configured as -tasks: - test: - command: 'jest' - args: - - '--testMatch' - - '@globs(tests)' - inputs: - - '@globs(tests)' - -# Resolves to -tasks: - test: - command: 'jest' - args: - - '--testMatch' - - 'tests/**/*' - - '**/__tests__/**/*' - inputs: - - '/path/to/project/tests/**/*' - - '/path/to/project/**/__tests__/**/*' -``` - -##### `@root` - -> Usable in `args` and `inputs`. - -The `@root(file_group)` token will be replaced with the lowest common directory, derived from the -file group of the same name. If a glob pattern is detected within the file group, it will walk the -file system and aggregate all directories found before reducing. - -When used in `args`, it will return relative paths, while `inputs` will return absolute paths. - -```yaml -fileGroups: - sources: - - 'src/app' - - 'src/packages' - - 'src/scripts' - -# Configured as -tasks: - format: - command: 'prettier' - args: - - '--write' - - '@root(sources)' - inputs: - - '@root(sources)' - -# Resolves to -tasks: - format: - command: 'prettier' - args: - - '--write' - - 'src' - inputs: - - '/path/to/project/src' -``` - -> When there's no directies, or too many directories, this function will return the project root -> using `.`. - -#### Inputs - -##### `@in` - -> Usable in `args` only. - -The `@in(index)` token will be replaced with a single path, derived from `inputs` by numerical -index. If a glob pattern is referenced by index, the glob will be used as-is, instead of returning -the expanded list of files. - -```yaml -# Configured as -tasks: - build: - command: 'babel' - args: - - '--copy-files' - - '--config-file' - - '@in(1)' - - '@in(0)' - inputs: - - 'src' - - 'babel.config.js' - -# Resolves to -tasks: - build: - command: 'babel' - args: - - '--copy-files' - - '--config-file' - - 'babel.config.js' - - 'src' - inputs: - - '/path/to/project/src' - - '/path/to/project/babel.config.js' -``` - -#### Outputs - -##### `@out` - -> Usable in `args` only. - -The `@out(index)` token will be replaced with a single path, derived from `outputs` by numerical -index. If a glob pattern is referenced by index, the process will **fail**, as it requires literal -folder and file paths. - -```yaml -# Configured as -tasks: - build: - command: 'babel' - args: - - '.' - - '--out-dir' - - '@out(0)' - outputs: - - 'lib' - -# Resolves to -tasks: - build: - command: 'babel' - args: - - '.' - - '--out-dir' - - 'lib' - outputs: - - '/path/to/project/lib' -``` - -### Variables - -> Usable in `args` only. - -A token variable is a value that starts with `$` and is substituted to a value derived from the -current workspace, project, and task. The following variables are available: - -- `$project` - Project ID of the project that owns the currently running task, as defined in - `.moon/workspace.yml`. -- `$projectSource` - Relative file path from the workspace root to the project root, as defined in - `.moon/workspace.yml`. -- `$projectRoot` - Absolute file path to the project root. -- `$target` - Target that is currently running. Is a combination of project and task IDs. -- `$task` - Task ID that is currently running. -- `$workspaceRoot` - Absolute file path to the workspace root. - -> Unlike token functions, token variables can be placed _within_ content when necessary. - -```yaml -# Configured as -tasks: - build: - command: 'example' - args: - - '$target' - - '--cache-dir' - - '../../.cache/$projectSource' - - '--project' - - '$project' - - '--task=$task' - -# Resolves to -tasks: - build: - command: 'example' - args: - - 'web:build' - - '--cache-dir' - - '../../.cache/apps/web' - - '--project' - - 'web' - - '--task=build' -``` - -## Targets - -A target is an identifier that pairs a project to an owned task, in the format of -"project_id:task_id". Targets are used by terminal commands... - -```shell -$ moon run project:build -``` - -And task configurations for declaring cross-project or cross-task dependencies. - -```yaml -tasks: - build: - command: 'webpack' - deps: - - 'dsl:build' -``` - -## Merge strategies - -When a [global task](./workspace.md#tasks) and [local task](./project.md#tasks) of the same name -exist, they are merged into a single task. To accomplish this, one of many -[merge strategies](./workspace.md#options) can be used. - -Merging is applied to the parameters `args`, `deps`, `env`, `inputs`, and `outputs`, using the -`mergeArgs`, `mergeDeps`, `mergeEnv`, `mergeInputs` and `mergeOutputs` options respectively. Each of -these options support one of the following strategy values. - -- `append` (default) - Values found in the local task are merged _after_ the items found in the - global task. For example, this strategy is useful for toggling flag arguments. -- `prepend` - Values found in the local task are merged _before_ the items found in the global task. - For example, this strategy is useful for applying option argument that must come before positional - arguments. -- `replace` - Values found in the local task entirely _replaces_ the value in the global task. This - strategy is useful when you need full control. - -All 3 of these strategies are demonstrated below, with a somewhat contrived example, but you get the -point. - -```yaml -# Global -tasks: - build: - command: 'webpack' - args: - - '--mode' - - 'production' - - '--color' - deps: - - 'dsl:build' - inputs: - - '/webpack.config.js' - outputs: - - 'build/' - -# Local -tasks: - build: - args: - - '--no-color' - - '--no-stats' - deps: - - 'hooks:build' - inputs: - - 'webpack.config.js' - options: - mergeArgs: 'append' - mergeDeps: 'prepend' - mergeInputs: 'replace' - -# Merged result -tasks: - build: - command: 'webpack' - args: - - '--mode' - - 'production' - - '--color' - - '--no-color' - - '--no-stats' - deps: - - 'hooks:build' - - 'dsl:build' - inputs: - - 'webpack.config.js' - options: - mergeArgs: 'append' - mergeDeps: 'prepend' - mergeInputs: 'replace' -``` diff --git a/docs/workspace.md b/docs/workspace.md deleted file mode 100644 index feed81719a7..00000000000 --- a/docs/workspace.md +++ /dev/null @@ -1,503 +0,0 @@ -# Workspace - -- [Configuration](#configuration) - - [`workspace.yml`](#workspaceyml) - - [projects](#projects) - - [node](#node) - - [version](#version) - - [packageManager](#packagemanager) - - [npm, pnpm, yarn](#npm-pnpm-yarn) - - [version](#version-1) - - [addEnginesConstraint](#addenginesconstraint) - - [dedupeOnLockfileChange](#dedupeoninstall) - - [syncProjectWorkspaceDependencies](#syncprojectworkspacedependencies) - - [syncVersionManagerConfig](#syncversionmanagerconfig) - - [typescript](#typescript) - - [projectConfigFileName](#projectconfigfilename) - - [rootConfigFileName](#rootconfigfilename) - - [syncProjectReferences](#syncprojectreferences) - - [vcs](#vcs) - - [manager](#manager) - - [defaultBranch](#defaultbranch) - - [`project.yml`](#projectyml) - - [fileGroups](#filegroups) - - [tasks](#tasks) - - [args](#args) - - [deps](#deps) - - [env](#env) - - [inputs](#inputs) - - [outputs](#outputs) - - [options](#options) - - [type](#type) - -A workspace is a directory that contains [projects](./project.md), manages a -[toolchain](./toolchain.md), and is typically coupled with a VCS repository. The root of a workspace -is denoted by a `.moon` folder and a `package.json`. - -By default Moon has been designed for monorepos, but can also be used for polyrepos. - -## Configuration - -Configurations that apply to the entire workspace are located within a `.moon` folder at the -workspace root. - -> This folder _must_ be relative to the root `package.json` and it's associated lock file. - -### `workspace.yml` - -The `.moon/workspace.yml` file configures projects and the toolchain. - -#### projects - -The `projects` setting is a map that defines the location of all [projects](./project.md) within the -workspace. Each project requires a unique ID as the map key, where this ID is used heavily on the -command line and within the project graph for uniquely identifying the project amongst all projects. -The map value (known as the project source) is a file system path to the project folder, relative -from the workspace root, and must be contained within the workspace boundary. - -```yaml -projects: - admin: 'apps/admin' - web: 'apps/web' - dsl: 'packages/design-system' - hooks: 'packages/react-hooks' -``` - -Unlike packages in the JavaScript ecosystem, a Moon project _does not_ require a `package.json`. - -> **Why doesn't Moon auto-detect projects?** Moon _does not_ automatically detect projects using -> file system globs for the following reasons: -> -> - Depth-first scans are expensive, especially when the workspace continues to grow. -> - CI and other machines may inadvertently detect more projects because of left over artifacts. -> - Centralizing a manifest of projects allows for an easy review and approval process. - -#### node - -The `node` setting defines the Node.js version and package manager to install within the toolchain, -as Moon _does not_ use a Node.js binary found on the host machine. Managing the Node.js version -within the toolchain ensures a deterministic environment across any machine (whether a developer, -CI, or production machine). - -This setting also houses any configuration for JavaScript, TypeScript, or the related ecosystem. - -> This setting is optional, and will default Node.js to the latest -> [active LTS version](https://nodejs.org/en/about/releases/) when not defined. - -##### version - -The `version` setting defines the explicit Node.js version to use. We require an explicit and -semantic major, minor, and patch version, to ensure the same environment is used across every -machine. - -```yaml -node: - version: '16.13.0' -``` - -> Version can be overridden with the `MOON_NODE_VERSION` environment variable. - -##### packageManager - -This setting defines which package manager to utilize within the workspace. Supports `npm` -(default), `pnpm`, or `yarn`. - -```yaml -node: - packageManager: 'yarn' -``` - -##### npm, pnpm, yarn - -The `npm`, `pnpm`, and `yarn` settings are _optional_ fields for defining package manager specific -configuration. The chosen setting is dependent on the value of `node.packageManager`. If these -settings _are not defined_, the latest version of the active package manager will be used (when -applicable). - -###### version - -The `version` setting defines the explicit package manager version to use. We require an explicit -major, minor, and patch version, to ensure the same environment is used across every machine. - -```yaml -node: - yarn: - version: '3.1.0' -``` - -> Version can be overridden with the `MOON_NPM_VERSION`, `MOON_PNPM_VERSION`, or -> `MOON_YARN_VERSION`, environment variables. - -##### addEnginesConstraint - -The `addEnginesConstraint` setting will inject the currently configured [Node.js version](#version) -as a constraint to the root `package.json` `engines` field. Defaults to `true`. - -```yaml -node: - addEnginesConstraint: true -``` - -For example, say our Node.js version is "16.14.0", and when we execute a run process through the -`moon` binary, it will update the root `package.json` with the below. We pin a fixed version to -ensure other Node.js processes outside of our toolchain are utilizing the same version. - -```jsonc -{ - // ... - "engines": { - "node": "16.14.0" - } -} -``` - -##### dedupeOnLockfileChange - -The `dedupeOnLockfileChange` setting will dedupe dependencies after they have been installed, in an -effort to keep the workspace tree as clean and lean as possible. Defaults to `true`. - -```yaml -node: - dedupeOnLockfileChange: true -``` - -##### syncProjectWorkspaceDependencies - -The `syncProjectWorkspaceDependencies` setting will sync the `dependsOn` setting within a project's -`project.yml` as normal dependencies within the project's `package.json`, using `workspace:*` or `*` -version ranges (depending on what the package manager supports). If a dependent project does not -have a `package.json`, or if a dependency of the same name has an explicit version already defined, -the sync will be skipped. Defaults to `true`. - -```yaml -node: - syncProjectWorkspaceDependencies: true -``` - -A quick example on how this works. Given the following `dependsOn`: - -```yaml -dependsOn: - - 'design-system' - - 'react-utils' -``` - -Would result in the following `dependencies` within a project's `package.json`. - -```jsonc -{ - // ... - "dependencies": { - "@company/design-system": "workspace:*", - "@company/react-utils": "workspace:*" - // ... - } -} -``` - -##### syncVersionManagerConfig - -The `syncVersionManagerConfig` setting syncs the currently configured [Node.js version](#version) to -a 3rd-party version manager's config/rc file. Supports `nodenv` (syncs to `.node-version`), `nvm` -(syncs to `.nvmrc`), or none (default). - -```yaml -node: - syncVersionManagerConfig: 'nvm' -``` - -This is a special setting that ensure other Node.js processes outside of our toolchain are utilizing -the same version, which is a very common practice when managing dependencies. - -#### typescript - -The `typescript` setting configures how Moon interacts with and utilizes TypeScript within the -workspace. - -##### projectConfigFileName - -The `projectConfigFileName` setting defines the name of the `tsconfig.json` found in the project -root. We utilize this setting when syncing project references between projects. Defaults to -`tsconfig.json`. - -```yaml -typescript: - projectConfigFileName: 'tsconfig.build.json' -``` - -##### rootConfigFileName - -The `rootConfigFileName` setting defines the name of the `tsconfig.json` found in the workspace -root. We utilize this setting when syncing projects as references. Defaults to `tsconfig.json`. - -```yaml -typescript: - rootConfigFileName: 'tsconfig.projects.json' -``` - -##### syncProjectReferences - -The `syncProjectReferences` setting will sync the `dependsOn` setting within a project's -`project.yml` as project references within that project's `tsconfig.json`, and the workspace root -`tsconfig.json`. Defaults to `true`. - -```yaml -typescript: - syncProjectReferences: true -``` - -A quick example on how this works. Given the following `dependsOn`: - -```yaml -dependsOn: - - design-system - - react-utils -``` - -Would result in the following `references` within both `tsconfig.json`s. - -```jsonc -{ - // ... - "references": [ - // ... - { "path": "../../design-system" }, - { "path": "../../react-utils" } - ] -} -``` - -#### vcs - -The `vcs` setting configures the version control system to utilize within the workspace (and -repository). A VCS is required for determining touched (added, modified, etc) files, calculating -file hashes, computing affected files, and much more. - -##### manager - -The `manager` setting definges the VCS tool/binary that is being used for managing the repository. -Accepts "git" (default) or "svn" (experimental). - -```yaml -vcs: - manager: 'git' -``` - -##### defaultBranch - -The `defaultBranch` setting defines the default upstream branch (master/main/trunk) in the -repository for comparing the local branch against. For git, this is is typically "origin/master" -(default) or "origin/main", and must include the remote prefix (before /). For svn, this should -always be "trunk". - -```yaml -vcs: - defaultBranch: 'origin/master' -``` - -### `project.yml` - -The `.moon/project.yml` file configures settings that are inherited by _every_ project in the -workspace. Projects can override these settings within their `/project.yml` -([view the projects docs for more information](./project.md#configuration)). - -#### fileGroups - -File groups are a mechanism for grouping similar types of files within a project using file glob -patterns. These groups are then used by tasks to calculate functionality like cache computation, -affected files since last change, command line arguments, deterministic builds, and more. - -This setting requires a map, where the key is the file group name, and the value is a list of globs. -Globs are relative to a project -- even though these are defined globally. This enables enforcement -of organizational patterns across all projects in the workspace. - -```yaml -fileGroups: - configs: - - '*.{js,json}' - sources: - - 'src/**/*' - - 'types/**/*' - tests: - - 'tests/**/*.test.*' - - '**/__tests__/**/*' - assets: - - 'assets/**/*' - - 'images/**/*' - - 'static/**/*' - - '**/*.{scss,css}' - - '**/*.mdx' -``` - -> The code snippet above is merely an example of file groups. Feel free to use those groups as-is, -> modify the glob lists, add and remove groups, or implement completely new groups. The choice is -> yours! - -#### tasks - -Tasks are actions that are ran within the context of a [project](./project.md), and commonly wrap an -npm or shell command. Tasks that are defined here and inherited by all projects within the -workspace, but can be overridden per project. - -This setting requires a map, where the key is a unique name for the task, and the value is an object -of task parameters. A `command` parameter is _required_ for each task. - -```yaml -tasks: - build: - command: 'webpack' - lint: - command: 'eslint' - test: - command: 'jest' - typecheck: - command: 'tsc' -``` - -> Learn more about tasks and its concepts in the [tasks documentation](./task.md). - -##### args - -The optional `args` param is a list of arguments to pass on the command line when executing the -task. - -```yaml -tasks: - test: - command: 'jest' - args: - - '--color' -``` - -For this to work correctly, each argument _must_ be its own list item, including argument values. -For example: - -```yaml -tasks: - test: - command: 'jest' - args: - # Valid - - '--maxWorkers' - - '3' - # Also valid - - '--maxWorkers=3' - # Invalid - - '--maxWorkers 3' -``` - -##### deps - -The optional `deps` param is a list of other project tasks, known as [targets](./task.md#targets), -that will be executed _before_ this task. It achieves this by generating a concurrent dependency -graph based on the project graph. - -```yaml -tasks: - build: - command: 'webpack' - deps: - - 'dsl:build' - - 'hooks:build' -``` - -##### env - -The optional `env` param is map of strings that are passed as environment variables when running the -command. - -```yaml -tasks: - build: - command: 'webpack' - env: - NODE_ENV: 'production' -``` - -##### inputs - -The optional `inputs` param is a list of file paths/globs that are used to calculate whether to -execute this task based on files that have been modified since the last time the task has been ran. -If no modified files align with the inputs, the task will complete instantly. - -By default inputs are relative from the _project root_, and can reference -[file groups](#filegroups). To reference files from the workspace root (for example, config files), -prefix the path with a "/". - -```yaml -tasks: - lint: - command: 'eslint' - inputs: - # Config files anywhere within the project - - '**/.eslintignore' - - '**/.eslintrc.js' - # Config files at the workspace root - - '/.eslintignore' - - '/.eslintrc.js' -``` - -##### outputs - -The optional `outputs` param is a list of files and folders that are created as a result of -executing this task, excluding internal cache files that are created from the underlying command. - -By default outputs are relative from the _project root_. To output files to the workspace root -(should be used rarely), prefix the path with a "/". - -```yaml -tasks: - build: - command: 'webpack' - outputs: - # Relative from project root - - 'build/' -``` - -##### options - -The optional `options` param is an object of configurable options that can be used to modify the -task and its execution. The following fields can be provided, with merge fields supporting all -[merge strategies](./task.md#merge-strategies). - -- `mergeArgs` (`TaskMergeStrategy`) - The strategy to use when merging the `args` list. Defaults to - "append". -- `mergeDeps` (`TaskMergeStrategy`) - The strategy to use when merging the `deps` list. Defaults to - "append". -- `mergeEnv` (`TaskMergeStrategy`) - The strategy to use when merging the `env` map. Defaults to - "append". -- `mergeInputs` (`TaskMergeStrategy`) - The strategy to use when merging the `inputs` list. Defaults - to "append". -- `mergeOutputs` (`TaskMergeStrategy`) - The strategy to use when merging the `outputs` list. - Defaults to "append". -- `retryCount` (`number`) - The amount of times the task execution will retry when it fails. - Defaults to `0`. -- `runInCi` (`boolean`) - Whether to run the task automatically in a CI pipeline (when affected by - modified files). Defaults to `true`, and is _always_ true when a task defines `outputs`. -- `runFromWorkspaceRoot` (`boolean`) - Whether to use the workspace root as the working directory - when executing a task. Defaults to `false` and runs from the task's project root. - -```yaml -tasks: - typecheck: - command: 'tsc' - args: - - '--noEmit' - options: - runFromWorkspaceRoot: true -``` - -##### type - -The optional `type` param defines the type of command to run, where to locate it, and which tool to -use. Accepts "node" or "shell" and defaults to "node". - -```yaml -tasks: - env: - command: 'printenv' - type: 'shell' -``` - -> This param exists because of our [toolchain](./toolchain.md), and Moon ensuring the correct -> command is ran. diff --git a/package.json b/package.json index e27034991fe..599c271d4ce 100644 --- a/package.json +++ b/package.json @@ -3,30 +3,32 @@ "private": true, "packageManager": "yarn@3.2.0", "scripts": { + "docs": "yarn moon run website:start", "version:apply": "node ./scripts/version/applyAndTagVersions.mjs", "version:bump": "yarn version check --interactive", "version:bump:bin": "bash ./scripts/version/bumpBinaryVersions.sh", - "build": "NODE_ENV=production packemon pack --addEngines --addExports --declaration=standard", + "build": "NODE_ENV=production packemon pack --addEngines --addExports --declaration", "type": "target/debug/moon --logLevel trace run :typecheck", "moon": "target/debug/moon --logLevel trace" }, "workspaces": [ - "packages/*" + "packages/*", + "website" ], "engines": { - "node": ">=12.17.0" + "node": ">=14.15.0" }, "devDependencies": { "@moonrepo/cli": "workspace:*", - "@types/node": "^17.0.24", - "eslint": "^8.13.0", - "eslint-config-beemo": "^1.2.8", - "jest": "^27.5.1", - "jest-preset-beemo": "^1.1.7", - "packemon": "^1.15.0", + "@types/node": "^17.0.32", + "eslint": "^8.15.0", + "eslint-config-beemo": "^1.2.10", + "jest": "^28.1.0", + "jest-preset-beemo": "^1.1.8", + "packemon": "^2.2.1", "prettier": "^2.6.2", "prettier-config-beemo": "^1.0.1", "tsconfig-beemo": "^1.0.1", - "typescript": "^4.6.3" + "typescript": "^4.6.4" } } diff --git a/packages/cli/LICENSE b/packages/cli/LICENSE index d5e5cab9101..6456ccc38a8 100644 --- a/packages/cli/LICENSE +++ b/packages/cli/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 Moon, Miles Johnson +Copyright (c) 2021 moon, Miles Johnson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, diff --git a/packages/cli/package.json b/packages/cli/package.json index e15dc9083ca..3a56e87a9f5 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@moonrepo/cli", "version": "0.0.2", - "description": "Moon command line and core system.", + "description": "moon command line and core system.", "keywords": [ "moon", "repo", @@ -37,8 +37,5 @@ "@moonrepo/core-linux-x64-musl": "^0.0.2", "@moonrepo/core-macos-x64": "^0.0.2", "@moonrepo/core-windows-x64-msvc": "^0.0.2" - }, - "engines": { - "node": ">=12.17.0" } } diff --git a/packages/core-linux-x64-gnu/package.json b/packages/core-linux-x64-gnu/package.json index 477bd2e0386..726f0efb302 100644 --- a/packages/core-linux-x64-gnu/package.json +++ b/packages/core-linux-x64-gnu/package.json @@ -1,7 +1,7 @@ { "name": "@moonrepo/core-linux-x64-gnu", "version": "0.0.2", - "description": "Linux binary for Moon.", + "description": "Linux binary for moon.", "keywords": [ "moon", "repo", @@ -25,9 +25,6 @@ "url": "https://github.com/milesj/moon", "directory": "packages/core-linux-x64-gnu" }, - "engines": { - "node": ">=12.17.0" - }, "publishConfig": { "access": "public" } diff --git a/packages/core-linux-x64-musl/package.json b/packages/core-linux-x64-musl/package.json index 6955540e262..7814a03a1b6 100644 --- a/packages/core-linux-x64-musl/package.json +++ b/packages/core-linux-x64-musl/package.json @@ -1,7 +1,7 @@ { "name": "@moonrepo/core-linux-x64-musl", "version": "0.0.2", - "description": "Linux binary for Moon.", + "description": "Linux binary for moon.", "keywords": [ "moon", "repo", @@ -25,9 +25,6 @@ "url": "https://github.com/milesj/moon", "directory": "packages/core-linux-x64-musl" }, - "engines": { - "node": ">=12.17.0" - }, "publishConfig": { "access": "public" } diff --git a/packages/core-macos-x64/package.json b/packages/core-macos-x64/package.json index 05381bc23ab..47bd89e2f29 100644 --- a/packages/core-macos-x64/package.json +++ b/packages/core-macos-x64/package.json @@ -1,7 +1,7 @@ { "name": "@moonrepo/core-macos-x64", "version": "0.0.2", - "description": "MacOS binary for Moon.", + "description": "MacOS binary for moon.", "keywords": [ "moon", "repo", @@ -22,9 +22,6 @@ "url": "https://github.com/milesj/moon", "directory": "packages/core-macos-x64" }, - "engines": { - "node": ">=12.17.0" - }, "publishConfig": { "access": "public" } diff --git a/packages/core-windows-x64-msvc/package.json b/packages/core-windows-x64-msvc/package.json index b4d69de9488..03ef5548c78 100644 --- a/packages/core-windows-x64-msvc/package.json +++ b/packages/core-windows-x64-msvc/package.json @@ -1,7 +1,7 @@ { "name": "@moonrepo/core-windows-x64-msvc", "version": "0.0.2", - "description": "Windows binary for Moon.", + "description": "Windows binary for moon.", "keywords": [ "moon", "repo", @@ -22,9 +22,6 @@ "url": "https://github.com/milesj/moon", "directory": "packages/core-windows-x64-msvc" }, - "engines": { - "node": ">=12.17.0" - }, "publishConfig": { "access": "public" } diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 36a0a3c3266..124c2f91d55 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -34,8 +34,8 @@ "@boost/common": "^3.2.1" }, "engines": { - "node": ">=12.17.0", - "npm": ">=6.13.0" + "node": ">=14.15.0", + "npm": ">=6.14.0" }, "exports": { "./package.json": "./package.json", @@ -43,10 +43,10 @@ "types": "./dts/*.d.ts" }, ".": { + "types": "./dts/index.d.ts", "node": { "import": "./mjs/index.mjs" - }, - "types": "./dts/index.d.ts" + } } } } diff --git a/packages/runtime/src/context.ts b/packages/runtime/src/context.ts index f1111585b80..2a45d8da89a 100644 --- a/packages/runtime/src/context.ts +++ b/packages/runtime/src/context.ts @@ -16,7 +16,7 @@ export async function getContext(): Promise { const { env } = process; if (!env.MOON_PROJECT_RUNFILE) { - throw new Error('Attempting to access Moon context outside of a run process.'); + throw new Error('Attempting to access moon context outside of a run process.'); } const project = json.parse(await fs.promises.readFile(env.MOON_PROJECT_RUNFILE, 'utf8')); diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index bdfbf1711c8..022dd7f51cb 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -7,6 +7,9 @@ "packages/*/src/**/*", "packages/*/tests/**/*", "packages/*/types/**/*", - "scripts/**/*" + "scripts/**/*", + "website/src/**/*", + "website/.eslintrc.js", + "website/*.js" ] } diff --git a/tsconfig.json b/tsconfig.json index bd27ea6c723..6e8e7b9d807 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,9 @@ "references": [ { "path": "packages/runtime" + }, + { + "path": "website" } ] } diff --git a/website/.eslintrc.js b/website/.eslintrc.js new file mode 100644 index 00000000000..91517fdf8bc --- /dev/null +++ b/website/.eslintrc.js @@ -0,0 +1,14 @@ +// eslint-disable-next-line import/no-commonjs +module.exports = { + ignorePatterns: ['prism.config.js', 'tailwind.config.js'], + rules: { + // This fails on windows for some reason + 'import/named': 'off', + + // Docusaurus requires default exports for components + 'import/no-default-export': 'off', + + // Tailwind composition + 'no-magic-numbers': 'off', + }, +}; diff --git a/website/.gitignore b/website/.gitignore new file mode 100644 index 00000000000..b2d6de30624 --- /dev/null +++ b/website/.gitignore @@ -0,0 +1,20 @@ +# Dependencies +/node_modules + +# Production +/build + +# Generated files +.docusaurus +.cache-loader + +# Misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/website/babel.config.js b/website/babel.config.js new file mode 100644 index 00000000000..6b5e0c86e62 --- /dev/null +++ b/website/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: [require.resolve('@docusaurus/core/lib/babel/preset')], +}; diff --git a/website/docs/commands/bin.mdx b/website/docs/commands/bin.mdx new file mode 100644 index 00000000000..8b4b8aff5be --- /dev/null +++ b/website/docs/commands/bin.mdx @@ -0,0 +1,20 @@ +--- +title: bin +--- + +The `bin ` command will return an absolute path to a tool's binary within the toolchain. If a +tool has not been configured or installed, this will return a 1 or 2 exit code with no value +respectively. + +```shell +$ moon bin node +/Users/example/.moon/tools/node/x.x.x/bin/node +``` + +A tool is considered "not configured" when not in use, for example, querying yarn/pnpm when the +package manager is configured for "npm". A tool is considered "not installed", when it has not been +downloaded and installed into the tools directory. + +### Arguments + +- `` - Name of the tool to query. Accepts "node", "npm", "pnpm", or "yarn". diff --git a/website/docs/commands/ci.mdx b/website/docs/commands/ci.mdx new file mode 100644 index 00000000000..2366d033099 --- /dev/null +++ b/website/docs/commands/ci.mdx @@ -0,0 +1,21 @@ +--- +title: ci +--- + +The `ci` command is a special command that should be ran in a continuous integration (CI) +environment, as it does all the heavy lifting necessary for effectively running tasks. + +```shell +$ moon ci +``` + +> View the official [continuous integration guide](../guides/ci) for a more in-depth example of how +> to utilize this command. + +### Options + +- `--base ` - Base branch, commit, or revision to compare against. Defaults to + [`vcs.defaultBranch`](../config/workspace#defaultbranch). +- `--head ` - Current branch, commit, or revision to compare with. Defaults to `HEAD`. +- `--job ` - Index of the current job. +- `--jobTotal ` Total amount of jobs to run. diff --git a/website/docs/commands/init.mdx b/website/docs/commands/init.mdx new file mode 100644 index 00000000000..d65c126a88e --- /dev/null +++ b/website/docs/commands/init.mdx @@ -0,0 +1,22 @@ +--- +title: init +--- + +The `init [dest]` command will initialize moon into a repository and scaffold config files by +creating a `.moon` folder. + +```shell +$ moon init + +# In another directory +$ moon init ./app +``` + +### Arguments + +- `[dest]` - Destination to initialize and scaffold into. Defaults to `.` (current working + directory). + +### Options + +- `--force` - Overwrite existing config files if they exist. diff --git a/website/docs/commands/project-graph.mdx b/website/docs/commands/project-graph.mdx new file mode 100644 index 00000000000..83eeb2c7c60 --- /dev/null +++ b/website/docs/commands/project-graph.mdx @@ -0,0 +1,37 @@ +--- +title: project-graph +--- + +The `project-graph [id]` command will generate a graph of all configured projects, with edges +between dependencies, and will output the graph in +[Graphviz DOT format](https://graphviz.org/doc/info/lang.html). This output can then be used by any +tool or program that supports DOT, for example, this +[live preview visualizer](https://dreampuf.github.io/GraphvizOnline). + +In the future we aim to replace this with an interactive visualizer. + +```shell +$ moon project-graph > graph.dot +``` + +> A project ID can be passed to focus the graph to only that project and it's dependencies. For +> example, `moon project-graph app`. + +### Arguments + +- `[id]` - Optional ID of a project to focus, as defined in + [`projects`](../config/workspace#projects). + +## Example output + +The following output is an example of a graph in DOT format. + +```dot +digraph { + 0 [ label="(workspace)" style=filled, shape=circle, fillcolor=black, fontcolor=white] + 1 [ label="runtime" style=filled, shape=circle, fillcolor=gray, fontcolor=black] + 2 [ label="website" style=filled, shape=circle, fillcolor=gray, fontcolor=black] + 0 -> 1 [ arrowhead=none] + 0 -> 2 [ arrowhead=none] +} +``` diff --git a/website/docs/commands/project.mdx b/website/docs/commands/project.mdx new file mode 100644 index 00000000000..72100776fe3 --- /dev/null +++ b/website/docs/commands/project.mdx @@ -0,0 +1,50 @@ +--- +title: project +--- + +The `project ` command will display all available information about a project that has been +configured and exists within the graph. If a project does not exist, the program will return with a +1 exit code. + +```shell +$ moon project web +``` + +### Arguments + +- `` - ID of a project, as defined in [`projects`](../config/workspace#projects). + +### Options + +- `--json` - Print the project and its configuration as JSON. + +## Example output + +The following output is an example of what this command prints, using our very own +`@moonrepo/runtime` package. + +``` +RUNTIME + +ID: runtime +Source: packages/runtime +Root: /Projects/moon/packages/runtime + +TASKS + +build: packemon build --addEngines --addExports +format: prettier --check . +lint: eslint --no-error-on-unmatched-pattern . +test: jest --passWithNoTests . +typecheck: tsc --build + +FILE GROUPS + +configs: + - *.{js,json} +sources: + - src/**/* + - types/**/* +tests: + - tests/**/*.test.* +``` diff --git a/website/docs/commands/run.mdx b/website/docs/commands/run.mdx new file mode 100644 index 00000000000..f5146e8910a --- /dev/null +++ b/website/docs/commands/run.mdx @@ -0,0 +1,31 @@ +--- +title: run +--- + +The `run` command will run a [target](../concepts/target) (a task within a project) and all of it's +dependencies in topological order. Each run will incrementally cache each task, improving speed and +development times... over time. + +```shell +# Run `lint` in project `app` +$ moon run app:lint + +# Run `lint` in all projects +$ moon run :lint +``` + +> View the official [Run a task](../run-task) article for more information! + +### Arguments + +- `` - [Target](../concepts/target) to run. +- `[-- ]` - Additional arguments to + [pass to the underlying command](#passing-arguments-to-the-underlying-command). + +### Options + +- `--affected` - Only run target if affected by changed files, _otherwise_ will always run. +- `--status ` - Filter affected based on a change status. + - Types: all (default), added, deleted, modified, staged, unstaged, untracked +- `--upstream` - Determine affected against upstream by comparing `HEAD` against a base revision + (default branch), _otherwise_ uses local changes. diff --git a/website/docs/commands/setup.mdx b/website/docs/commands/setup.mdx new file mode 100644 index 00000000000..529f549ee6d --- /dev/null +++ b/website/docs/commands/setup.mdx @@ -0,0 +1,17 @@ +--- +title: setup +--- + +The `setup` command can be used to setup the developer and pipeline environments. It achieves this +by downloading and installing all configured tools into the toolchain. + +```shell +$ moon setup +``` + +:::info + +This command should rarely be used, as the environment is automatically setup when running other +commands, like detecting affected projects, running a task, or generating a build artifact. + +::: diff --git a/website/docs/commands/teardown.mdx b/website/docs/commands/teardown.mdx new file mode 100644 index 00000000000..f5e87673a6c --- /dev/null +++ b/website/docs/commands/teardown.mdx @@ -0,0 +1,13 @@ +--- +title: teardown +--- + +The `teardown` command, as its name infers, will teardown and clean the current environment, +opposite the [`setup`](./setup) command. It achieves this by doing the following: + +- Uninstalling all configured tools in the toolchain. +- Removing any download or temporary files/folders. + +```shell +$ moon teardown +``` diff --git a/docs/cache.md b/website/docs/concepts/cache.mdx similarity index 75% rename from docs/cache.md rename to website/docs/concepts/cache.mdx index 0eb8a9a2324..122715ed627 100644 --- a/docs/cache.md +++ b/website/docs/concepts/cache.mdx @@ -1,12 +1,15 @@ -# Cache +--- +title: Cache +--- -Moon's able to achieve high performance and blazing speeds by implementing a cache that's powered by -our own unique smart hashing layer. +moon's able to achieve high performance and blazing speeds by implementing a cache that's powered by +our own unique smart hashing layer. All cache is stored in `.moon/cache`, relative from the +workspace root (be sure to git ignore this folder). ## Hashing Incremental builds are possible through a concept known as hashing, where in multiple sources are -aggregated to generate a unique hash. In the context of Moon, each time a target is ran we generate +aggregated to generate a unique hash. In the context of moon, each time a target is ran we generate a hash, and if this hash already exists we abort early (cache hit), otherwise we continue the run (cache miss). @@ -27,9 +30,9 @@ Our smart hashing currently takes the following sources into account: The following diagram outlines our cache folder structure and why each piece exists. -```sh +```shell .moon/cache/ - # State of the entire workspace. Mainly for tracking install times. + # State of the workspace. Mainly for tracking install times. workspaceState.json # Stores hashes of every ran task. Exists purely for debugging purposes. @@ -43,11 +46,11 @@ The following diagram outlines our cache folder structure and why each piece exi / ... - # State of tasks that have been ran or are running, grouped by project and task. + # State of targets that have been ran or are running, grouped by project and task. runs/ / # Information about the project, its tasks, and its configs. - # Can be used by tasks at runtime that require this information. + # Can be used at runtime by tasks that require this information. runfile.json / diff --git a/website/docs/concepts/project.mdx b/website/docs/concepts/project.mdx new file mode 100644 index 00000000000..5f27b772a9e --- /dev/null +++ b/website/docs/concepts/project.mdx @@ -0,0 +1,45 @@ +--- +title: Projects +--- + +A project is a library, application, package, binary, tool, etc, that contains source files, test +files, assets, resources, and more. A project must exist and be configured within a +[workspace](./workspace). + +## ID + +A project identifier is a unique resource for locating a project. The ID is explicitly configured +within [`.moon/workspace.yml`](../config/workspace), as a key within the +[`projects`](../config/workspace#projects) setting, and can be written in camelCase, kebab-case, or +snake_case. + +IDs are used heavily by configuration and the command line to link and reference everything. They're +also a much easier concept for remembering projects than file system paths, and they typically can +be written with less key strokes. + +Lastly, a project ID can be paired with a task ID to create a [target](./target). + +## Configuration + +Projects can be configured with an optional [`project.yml`](../config/project) in the project root, +or through the [`.moon/project.yml`](../config/global-project) which applies to all projects. + +### `package.json` + +A moon project _does not require_ a `package.json`, but when one exists, the following functionality +is enabled. + +- Dependency versions are included when computing cache keys. +- Depended on projects ([`dependsOn`](../config/project#dependson)) are mapped as npm/pnpm/yarn + workspace dependencies (when applicable). + +### `tsconfig.json` + +A moon project _does not require_ TypeScript or a `tsconfig.json`, but when one exists, the +following functionality is enabled. + +- Depended on projects ([`dependsOn`](../config/project#dependson)) are mapped as TypeScript project + references (when applicable). + +> File name can be customized with the +> [`typescript.projectConfigFileName`](../config/workspace#projectconfigfilename) setting. diff --git a/website/docs/concepts/target.mdx b/website/docs/concepts/target.mdx new file mode 100644 index 00000000000..8289883f947 --- /dev/null +++ b/website/docs/concepts/target.mdx @@ -0,0 +1,101 @@ +--- +title: Targets +--- + +A target is an identifier that pairs a [project](./project) to one of its [tasks](./task), in the +format of `project_id:task_id`. + +Targets are used by terminal commands... + +```shell +$ moon run designSystem:build +``` + +And configurations for declaring cross-project or cross-task dependencies. + +```yaml +tasks: + build: + command: 'webpack' + deps: + - 'designSystem:build' +``` + +## Project scopes + +While a target typically pairs project and task IDs, we also support advanced targets through a +concept known as project scopes. Scopes allow us to easily define targets with external or many +sources, but _are not available in all contexts_. + +### All projects + +> Only available on the command line when running targets. + +For situations where you want to run a specific target in _all_ projects, for example `lint`ing, you +can utilize the all projects scope by omitting the project from the target: `:lint`. + +```shell +# Run `lint` in project `app` +$ moon run app:lint + +# Run `lint` in all projects +$ moon run :lint +``` + +### Dependencies `^` + +> Only available when configuring a task. + +When you want to run a task for each project in your [`dependsOn`](../config/project#dependson) +list, you can utilize the `^` scope. This will be expanded to _all_ depended on projects. If you do +not want all projects, then you'll need to explicitly define them using identifiers. + +```yaml title="project.yml" +dependsOn: + - 'apiClients' + - 'designSystem' + +# Configured as +tasks: + build: + command: 'webpack' + deps: + - '^:build' + +# Resolves to +tasks: + build: + command: 'webpack' + deps: + - 'apiClients:build' + - 'designSystem:build' +``` + +### Self `~` + +> Only available when configuring a task. + +When referring to another task within the current project, you can utilize the `~` scope, which will +be expanded to the current project's identifier. This is useful for situations where the identifier +is unknown, for example, when configuring [`.moon/project.yml`](../config/global-project). Or if you +just want a shortcut! + +```yaml title=".moon/project.yml" +# Configured as +tasks: + lint: + command: 'eslint' + deps: + - '~:typecheck' + typecheck: + command: 'tsc' + +# Resolves to (assuming project is "foo") +tasks: + lint: + command: 'eslint' + deps: + - 'foo:typecheck' + typecheck: + command: 'tsc' +``` diff --git a/website/docs/concepts/task.mdx b/website/docs/concepts/task.mdx new file mode 100644 index 00000000000..fee53bec36d --- /dev/null +++ b/website/docs/concepts/task.mdx @@ -0,0 +1,97 @@ +--- +title: Tasks +--- + +Tasks are commands that are ran in the context of a [project](./project). Underneath the hood, a +task is simply an npm binary or a system command that is ran as a child process. Tasks are processed +in a parallel thread pool within moon's orchestration layer. + +## ID + +A task identifier is a unique resource for locating a task _within_ a project. The ID is explicitly +configured as a key within the [`tasks`](../config/project#tasks) setting, and can be written in +camelCase, kebab-case, or snake_case. + +A task ID can be paired with a project ID to create a [target](./target). + +## Configuration + +Tasks can be configured per project through [`project.yml`](../config/project), or for all projects +through [`.moon/project.yml`](../config/global-project). + +## Merge strategies + +When a [global task](../config/global-project#tasks) and [local task](../config/project#tasks) of +the same name exist, they are merged into a single task. To accomplish this, one of many +[merge strategies](../config/project#options) can be used. + +Merging is applied to the parameters [`args`](../config/project#args), +[`deps`](../config/project#deps), [`env`](../config/project#env), +[`inputs`](../config/project#inputs), and [`outputs`](../config/project#outputs), using the +[`mergeArgs`](../config/project#mergeargs), [`mergeDeps`](../config/project#mergedeps), +[`mergeEnv`](../config/project#mergeenv), [`mergeInputs`](../config/project#mergeinputs) and +[`mergeOutputs`](../config/project#mergeoutputs) options respectively. Each of these options support +one of the following strategy values. + +- `append` (default) - Values found in the local task are merged _after_ the values found in the + global task. For example, this strategy is useful for toggling flag arguments. +- `prepend` - Values found in the local task are merged _before_ the values found in the global + task. For example, this strategy is useful for applying option arguments that must come before + positional arguments. +- `replace` - Values found in the local task entirely _replaces_ the values in the global task. This + strategy is useful when you need full control. + +All 3 of these strategies are demonstrated below, with a somewhat contrived example, but you get the +point. + +```yaml +# Global +tasks: + build: + command: 'webpack' + args: + - '--mode' + - 'production' + - '--color' + deps: + - 'designSystem:build' + inputs: + - '/webpack.config.js' + outputs: + - 'build/' + +# Local +tasks: + build: + args: '--no-color --no-stats' + deps: + - 'reactHooks:build' + inputs: + - 'webpack.config.js' + options: + mergeArgs: 'append' + mergeDeps: 'prepend' + mergeInputs: 'replace' + +# Merged result +tasks: + build: + command: 'webpack' + args: + - '--mode' + - 'production' + - '--color' + - '--no-color' + - '--no-stats' + deps: + - 'reactHooks:build' + - 'designSystem:build' + inputs: + - 'webpack.config.js' + outputs: + - 'build/' + options: + mergeArgs: 'append' + mergeDeps: 'prepend' + mergeInputs: 'replace' +``` diff --git a/website/docs/concepts/token.mdx b/website/docs/concepts/token.mdx new file mode 100644 index 00000000000..a10d4a6fb33 --- /dev/null +++ b/website/docs/concepts/token.mdx @@ -0,0 +1,383 @@ +--- +title: Tokens +--- + +Tokens are variables and functions that can be used by [`args`](../config/project#args), +[`inputs`](../config/project#inputs), and [`outputs`](../config/project#outputs) when configuring a +task. They provide a way of accessing file group paths, referencing values from other task fields, +and referencing metadata about the project and task itself. + +## Functions + +A token function is labeled as such as it takes a single argument, starts with an `@`, and is +formatted as `@name(arg)`. The following token functions are available, grouped by their +functionality. + +> Token functions _must_ be the only content within a list item, as they expand to multiple file +> paths. + +### File groups + +These functions reference file groups by name, where the name is passed as the argument. + +### `@dirs` + +> Usable in `args` and `inputs`. + +The `@dirs(file_group)` token will be replaced with an expanded list of directory paths, derived +from the file group of the same name. If a glob pattern is detected within the file group, it will +walk the file system and aggregate all directories found. + +When used in [`args`](../config/project#args), it will return relative or absolute paths, depending +on run context, while [`inputs`](../config/project#inputs) will return absolute paths. + +```yaml +fileGroups: + lintable: + - 'src' + - 'tests' + - 'scripts' + +# Configured as +tasks: + lint: + command: 'eslint' + args: '@dirs(lintable) --color' + inputs: + - '@dirs(lintable)' + +# Resolves to +tasks: + lint: + command: 'eslint' + args: + - 'src' + - 'tests' + - 'scripts' + - '--color' + inputs: + - '/path/to/project/src' + - '/path/to/project/tests' + - '/path/to/project/scripts' +``` + +### `@files` + +> Usable in `args` and `inputs`. + +The `@files(file_group)` token will be replaced with an expanded list of file paths, derived from +the file group of the same name. If a glob pattern is detected within the file group, it will walk +the file system and aggregate all files found. + +When used in [`args`](../config/project#args), it will return relative or absolute paths, depending +on run context, while [`inputs`](../config/project#inputs) will return absolute paths. + +```yaml +fileGroups: + config: + - '*.config.js' + - 'package.json' + +# Configured as +tasks: + build: + command: 'webpack' + args: 'build @files(config)' + inputs: + - '@files(config)' + +# Resolves to +tasks: + build: + command: 'webpack' + args: + - 'build' + - 'babel.config.js' + - 'webpack.config.js' + - 'package.json' + inputs: + - '/path/to/project/babel.config.js' + - '/path/to/project/webpack.config.js' + - '/path/to/project/package.json' +``` + +### `@globs` + +> Usable in `args` and `inputs`. + +The `@globs(file_group)` token will be replaced with an expanded list of glob patterns (as-is), +derived from the file group of the same name. If a non-glob pattern is detected within the file +group, it will be ignored. + +When used in [`args`](../config/project#args), it will return relative or absolute paths, depending +on run context, while [`inputs`](../config/project#inputs) will return absolute paths _and_ also be +used in affected files detection by matching against the patterns. + +```yaml +fileGroups: + tests: + - 'tests/**/*' + - '**/__tests__/**/*' + +# Configured as +tasks: + test: + command: 'jest' + args: '--testMatch @globs(tests)' + inputs: + - '@globs(tests)' + +# Resolves to +tasks: + test: + command: 'jest' + args: + - '--testMatch' + - 'tests/**/*' + - '**/__tests__/**/*' + inputs: + - '/path/to/project/tests/**/*' + - '/path/to/project/**/__tests__/**/*' +``` + +### `@root` + +> Usable in `args` and `inputs`. + +The `@root(file_group)` token will be replaced with the lowest common directory, derived from the +file group of the same name. If a glob pattern is detected within the file group, it will walk the +file system and aggregate all directories found before reducing. + +When used in [`args`](../config/project#args), it will return relative paths, while +[`inputs`](../config/project#inputs) will return absolute paths. + +```yaml +fileGroups: + sources: + - 'src/app' + - 'src/packages' + - 'src/scripts' + +# Configured as +tasks: + format: + command: 'prettier' + args: '--write @root(sources)' + inputs: + - '@root(sources)' + +# Resolves to +tasks: + format: + command: 'prettier' + args: + - '--write' + - 'src' + inputs: + - '/path/to/project/src' +``` + +> When there's no directies, or too many directories, this function will return the project root +> using `.`. + +### Inputs & outputs + +### `@in` + +> Usable in `args` only. + +The `@in(index)` token will be replaced with a single path, derived from +[`inputs`](../config/project#inputs) by numerical index. If a glob pattern is referenced by index, +the glob will be used as-is, instead of returning the expanded list of files. + +```yaml +# Configured as +tasks: + build: + command: 'babel' + args: + - '--copy-files' + - '--config-file' + - '@in(1)' + - '@in(0)' + inputs: + - 'src' + - 'babel.config.js' + +# Resolves to +tasks: + build: + command: 'babel' + args: + - '--copy-files' + - '--config-file' + - 'babel.config.js' + - 'src' + inputs: + - '/path/to/project/src' + - '/path/to/project/babel.config.js' +``` + +### `@out` + +> Usable in `args` only. + +The `@out(index)` token will be replaced with a single path, derived from +[`outputs`](../config/project#outputs) by numerical index. If a glob pattern is referenced by index, +the process will **fail**, as it requires literal folder and file paths. + +```yaml +# Configured as +tasks: + build: + command: 'babel' + args: + - '.' + - '--out-dir' + - '@out(0)' + outputs: + - 'lib' + +# Resolves to +tasks: + build: + command: 'babel' + args: + - '.' + - '--out-dir' + - 'lib' + outputs: + - '/path/to/project/lib' +``` + +## Variables + +> Usable in `args` only. + +A token variable is a value that starts with `$` and is substituted to a value derived from the +current workspace, project, and task. And unlike token functions, token variables can be placed +_within_ content when necessary. + +### `$project` + +ID of the project that owns the currently running task, as defined in +[`.moon/workspace.yml`](../config/workspace). + +```yaml +# Configured as +tasks: + build: + command: 'example' + args: '--project $project' + +# Resolves to +tasks: + build: + command: 'example' + args: + - '--project' + - 'web' +``` + +### `$projectSource` + +Relative file path from the workspace root to the project root, as defined in +[`.moon/workspace.yml`](../config/workspace). + +```yaml +# Configured as +tasks: + build: + command: 'example' + args: + - '--cache-dir' + - '../../.cache/$projectSource' + +# Resolves to +tasks: + build: + command: 'example' + args: + - '--cache-dir' + - '../../.cache/apps/web' +``` + +### `$projectRoot` + +Absolute file path to the project root. + +```yaml +# Configured as +tasks: + build: + command: 'example' + args: '--cwd $projectRoot' + +# Resolves to +tasks: + build: + command: 'example' + args: + - '--cwd' + - '/path/to/repo/apps/web' +``` + +### `$target` + +Target that is currently running. Is a combination of project and task IDs. + +```yaml +# Configured as +tasks: + build: + command: 'example' + args: '$target' + +# Resolves to +tasks: + build: + command: 'example' + args: + - 'web:build' +``` + +### `$task` + +ID of the task that is currently running. + +```yaml +# Configured as +tasks: + build: + command: 'example' + args: '--task=$task' + +# Resolves to +tasks: + build: + command: 'example' + args: + - '--task=build' +``` + +### `$workspaceRoot` + +Absolute file path to the workspace root. + +```yaml +# Configured as +tasks: + build: + command: 'example' + args: + - '--cwd' + - '$workspaceRoot' + +# Resolves to +tasks: + build: + command: 'example' + args: + - '--cwd' + - '/path/to/repo' +``` diff --git a/docs/toolchain.md b/website/docs/concepts/toolchain.mdx similarity index 72% rename from docs/toolchain.md rename to website/docs/concepts/toolchain.mdx index 25af87e4e77..31b7326daec 100644 --- a/docs/toolchain.md +++ b/website/docs/concepts/toolchain.mdx @@ -1,12 +1,6 @@ -# Toolchain - -- [How it works](#how-it-works) -- [Configuration](#configuration) -- [Supported tools](#supported-tools) - - [Node.js](#nodejs) - - [npm](#npm) - - [pnpm](#pnpm) - - [yarn](#yarn) +--- +title: Toolchain +--- The toolchain is an internal layer for downloading, installing, and managing tools (languages, libraries, and binaries) that are required at runtime. We embrace this approach over relying on @@ -37,7 +31,7 @@ tool to `~/.moon/tools//`. ## Configuration The tools that are managed by the toolchain are configured through the -[`.moon/workspace.yml`](./workspace.md#workspaceyml) file. +[`.moon/workspace.yml`](../config/workspace) file. ## Supported tools @@ -48,36 +42,36 @@ The following tools will be managed by the toolchain. ### Node.js -Since Moon was designed for JavaScript based monorepo's, we intentionally support Node.js as a +Since moon was designed for JavaScript based repo's, we intentionally support Node.js as a first-class citizen within the toolchain. Because of this, Node.js is _always enabled_. -- Configured with: `node` +- Configured with: [`node`](../config/workspace#node) - Installed to: `~/.moon/tools/node/x.x.x` #### npm The `npm` binary comes pre-installed with Node.js, and will _always exist_, regardless of the -`node.packageManager` setting. +[`node.packageManager`](../config/workspace#packagemanager) setting. -- Configured with: `node.npm` +- Configured with: [`node.npm`](../config/workspace#npm-pnpm-yarn) - Installed to: `~/.moon/tools/node/x.x.x/bin/npm` (and `npx`) #### pnpm The [`pnpm`](https://pnpm.io) library can be used as an alternative package manager to npm, and will -be enabled when `node.packageManager` is set to "pnpm". The binary will be installed as a toolchain -global npm dependency. +be enabled when [`node.packageManager`](../config/workspace#packagemanager) is set to "pnpm". The +binary will be installed as a toolchain global npm dependency. -- Configured with: `node.pnpm` +- Configured with: [`node.pnpm`](../config/workspace#npm-pnpm-yarn) - Installed to: `~/.moon/tools/node/x.x.x/bin/pnpm` #### yarn The [`yarn`](https://yarnpkg.com) library can be used as an alternative package manager to npm, and -will be enabled when `node.packageManager` is set to "yarn". The binary will be installed as a -toolchain global npm dependency. +will be enabled when [`node.packageManager`](../config/workspace#packagemanager) is set to "yarn". +The binary will be installed as a toolchain global npm dependency. -- Configured with: `node.yarn` +- Configured with: [`node.yarn`](../config/workspace#npm-pnpm-yarn) - Installed to: `~/.moon/tools/node/x.x.x/bin/yarn` -> Supports v1 and v2/v3 in `node-modules` or `pnp` linker mode. +> Supports v1 and v2/v3 (only `node-modules` linker mode currently). diff --git a/website/docs/concepts/workspace.mdx b/website/docs/concepts/workspace.mdx new file mode 100644 index 00000000000..1c99e2f3a13 --- /dev/null +++ b/website/docs/concepts/workspace.mdx @@ -0,0 +1,14 @@ +--- +title: Workspace +--- + +A workspace is a directory that contains [projects](./project), manages a [toolchain](./toolchain), +runs [tasks](./task), and is coupled with a VCS repository. The root of a workspace is denoted by a +`.moon` folder and a `package.json`. + +By default moon has been designed for monorepos, but can also be used for polyrepos. + +## Configuration + +Configuration that's applied to the entire workspace is defined in +[`.moon/workspace.yml`](../config/workspace). diff --git a/website/docs/config/global-project.mdx b/website/docs/config/global-project.mdx new file mode 100644 index 00000000000..d56923e5348 --- /dev/null +++ b/website/docs/config/global-project.mdx @@ -0,0 +1,78 @@ +--- +title: .moon/project.yml +--- + +The `.moon/project.yml` file configures file groups and tasks that are inherited by _every_ project +in the workspace. Projects can override or merge with these settings within their respective +[`project.yml`](./project). + +## `fileGroups` + +> `Record` + +> For more information on file group configuration, refer to the +> [`fileGroups`](./project#filegroups) section in the [`project.yml`](./project) doc. + +As mentioned in the link above, file groups are a mechanism for grouping similar types of files +within a project using file glob patterns or literal file paths. File groups defined here enables +enforcement of organizational patterns and file locations. + +For example, encourage all projects to place source files in a `src` folder, and all test files in +`tests`. + +```yaml title=".moon/project.yml" +fileGroups: + configs: + - '*.{js,json}' + sources: + - 'src/**/*' + - 'types/**/*' + tests: + - 'tests/**/*.test.*' + - '**/__tests__/**/*' + assets: + - 'assets/**/*' + - 'images/**/*' + - 'static/**/*' + - '**/*.{scss,css}' + - '**/*' +``` + +> Relative file paths and globs used within a file group are relative from the inherited project's +> root, and not the workspace. + +## `tasks` + +> `Record` + +> For more information on task configuration, refer to the [`tasks`](./project#tasks) section in the +> [`project.yml`](./project) doc. + +As mentioned in the link above, [tasks](../concepts/task) are actions that are ran within the +context of a project, and commonly wrap an npm binary or system command. For most workspaces, every +project _should_ have linting, type-checking, testing, code formatting, so on and so forth. To +reduce the amount of boilerplate that _every_ project would require, this setting offers the ability +to define tasks that are inherited by all projects within the workspace, but can also be overridden +per project. + +```yaml title=".moon/project.yml" +tasks: + format: + command: 'prettier' + args: '--check .' + + lint: + command: 'eslint' + args: '--no-error-on-unmatched-pattern .' + + test: + command: 'jest' + args: '--passWithNoTests .' + + typecheck: + command: 'tsc' + args: '--build' +``` + +> Relative file paths and globs used within a task are relative from the inherited project's root, +> and not the workspace. diff --git a/website/docs/config/project.mdx b/website/docs/config/project.mdx new file mode 100644 index 00000000000..bf81c50f68b --- /dev/null +++ b/website/docs/config/project.mdx @@ -0,0 +1,493 @@ +--- +title: project.yml +toc_max_heading_level: 6 +--- + +import RequiredLabel from '@site/src/components/Docs/RequiredLabel'; + +The `project.yml` configuration file _is not required_ but can be used to define additional metadata +for a project, override inherited tasks, and more at the project-level. When used, this file must +exist in a project's root, as configured in [`projects`](./workspace#projects). + +## `dependsOn` + +> `ProjectID[]` + +Defines _other_ projects that _this_ project depends on, primarily when generating the project and +task graphs. The most common use case for this is building those projects _before_ building this +one, and for syncing [`package.json`](../concepts/project#packagejson) dependencies and +[`tsconfig.json`](../concepts/project#tsconfigjson) project references when applicable. + +When defined, this setting requires an array of project IDs, which are the keys found in the +[`projects`](./workspace#projects) map. + +```yaml title="project.yml" +dependsOn: + - 'apiClients' + - 'designSystem' +``` + +## `fileGroups` + +> `Record` + +File groups are a mechanism for grouping similar types of files within a project using file glob +patterns or literal file paths. These groups are then used by [tasks](#tasks) to calculate +functionality like cache computation, affected files since last change, deterministic builds, and +more. By default, this setting _is not required_ for the following reasons: + +- File groups are an optional feature, and are designed for advanced use cases. +- File groups defined in [`.moon/project.yml`](./global-project) will be inherited by all projects. + +When defined this setting requires a map, where the key is the file group name, and the value is a +list of globs or paths. Globs and paths are relative to a project (even when defined +[globally](./global-project)). + +```yaml title="project.yml" +fileGroups: + configs: + - '*.{js,json}' + sources: + - 'src/**/*' + - 'types/**/*' + tests: + - 'tests/**/*.test.*' + - '**/__tests__/**/*' + assets: + - 'assets/**/*' + - 'images/**/*' + - 'static/**/*' + - '**/*.{scss,css}' + - '**/*' +``` + +> The code snippet above is merely an example of file groups. Feel free to use those groups as-is, +> modify the glob lists, add and remove groups, or implement completely new groups. The choice is +> yours! + +### Inherited file groups + +A primary scenario in which to define file groups at the project-level is when you want to +_override_ file groups defined at the workspace-level. For example, say we want to override the +`sources` file group because our source folder is named "lib" and not "src", we would define our +file groups as followed. + +```yaml title=".moon/project.yml" +fileGroups: + sources: + - 'src/**/*' + - 'types/**/*' + tests: + - 'tests/**/*.test.*' + - '**/__tests__/**/*' +``` + +```yaml title="project.yml" +fileGroups: + sources: + - 'lib/**/*' + - 'types/**/*' + # Inherit `tests` as-is +``` + +File groups defined in `project.yml` will override file groups defined in `.moon/project.yml` of the +same name, and _will not_ merge the value arrays. + +## `project` + +> `ProjectMetadataConfig` + +The `project` setting defines metadata about the project itself. Although this setting is optional, +when defined, all fields within it _must_ be defined as well. + +```yaml title="project.yml" +project: + type: 'tool' + name: 'moon' + description: 'A monorepo management tool.' + channel: '#moon' + owner: 'infra.platform' + maintainers: ['miles.johnson'] +``` + +The information listed within `project` is purely informational and primarily displayed within the +CLI. However, this setting exists for you, your team, and your company, as a means to identify and +organize all projects. Feel free to build your own tooling around these settings! + +### `channel` + +> `string` + +The Slack, Discord, Teams, IRC, etc channel name (with leading #) in which to discuss the project. + +### `description` + +> `string` + +A description of what the project does and aims to achieve. Be as descriptive as possible, as this +is the kind of information search engines would index on. + +### `maintainers` + +> `string[]` + +A list of people/developers that maintain the project, review code changes, and can provide support. +Can be a name, email, LDAP name, GitHub username, etc, the choice is yours. + +### `name` + +> `string` + +A human readable name of the project. This is _different_ from the unique project ID configured in +[`projects`](./workspace#projects). + +### `owner` + +> `string` + +The team or organization that owns the project. Can be a title, LDAP name, GitHub team, etc. We +suggest _not_ listing people/developers as the owner, use [maintainers](#maintainers) instead. + +### `type` + +> `ProjectType` + +The type of project. Supports the following values: + +- `application` - A backend or frontend application that communicates over HTTP, TCP, RPC, etc. +- `library` - A self-contained, shareable, and publishable set of code. +- `tool` - An internal tool, command line program, one-off script, etc. + +## `tasks` + +> `Record` + +Tasks are actions that are ran within the context of a [project](../concepts/project), and commonly +wrap an npm binary or system command. This setting requires a map, where the key is a unique +identifier for the task, and the value is an object of task parameters. + +```yaml title="project.yml" +tasks: + format: + command: 'prettier' + lint: + command: 'eslint' + test: + command: 'jest' + typecheck: + command: 'tsc' +``` + +### `command` + +> `string` + +The `command` field is the name of an npm binary or native system command to run when executing the +task. This field is required when _not_ inheriting a global task of the same name. + +```yaml title="project.yml" {3} +tasks: + format: + command: 'prettier' +``` + +By default a task assumes the command is an npm binary, and if you'd like to reference a system +command, you'll also need to set the [`type`](#type) to "system". + +```yaml title="project.yml" +tasks: + clean: + command: 'rm' + args: '-rf dist' + type: 'system' +``` + +### `args` + +> `string | string[]` + +The `args` field is a collection of arguments to pass on the command line when executing the task. +This setting can be defined using a string, or an array of strings. We suggest using arrays when +dealing with many args, or the args string cannot be parsed easily. + +```yaml title="project.yml" {4,5} +tasks: + test: + command: 'jest' + # String + args: '--color --maxWorkers 3' + # Array + args: + - '--color' + - '--maxWorkers' + - '3' +``` + +However, for the array approach to work correctly, each argument _must_ be its own distinct item, +including argument values. For example: + +```yaml title="project.yml" +tasks: + test: + command: 'jest' + args: + # Valid + - '--maxWorkers' + - '3' + # Also valid + - '--maxWorkers=3' + # Invalid + - '--maxWorkers 3' +``` + +### `deps` + +> `Target[]` + +The `deps` field is a list of other tasks (known as [targets](../concepts/target)), either within +this project or found in another project, that will be executed _before_ this task. It achieves this +by generating a directed task graph based on the project graph. + +```yaml title="project.yml" {4-6} +tasks: + build: + command: 'webpack' + deps: + - 'apiClients:build' + - 'designSystem:build' +``` + +### `env` + +> `Record` + +The `env` field is map of strings that are passed as environment variables when running the command. + +```yaml title="project.yml" {4,5} +tasks: + build: + command: 'webpack' + env: + NODE_ENV: 'production' +``` + +### `inputs` + +> `string[]` + +The `inputs` field is a list of file paths/globs that are used to calculate whether to execute this +task based on files that have been touched since the last time the task has been ran. If _not_ +defined, then all files within a project are considered an input (`**/*`). + +By default inputs are relative from the _project root_, and can reference +[file groups](#filegroups). To reference files from the workspace root (for example, config files), +prefix the path with a "/". + +```yaml title="project.yml" {4-10} +tasks: + lint: + command: 'eslint' + inputs: + # Config files anywhere within the project + - '**/.eslintignore' + - '**/.eslintrc.js' + # Config files at the workspace root + - '/.eslintignore' + - '/.eslintrc.js' +``` + +### `outputs` + +> `string[]` + +The `outputs` field is a list of files and folders that are _created_ as a result of executing this +task, excluding internal cache files that are created from the underlying command (for example, +`.eslintcache`). + +By default outputs are relative from the _project root_. To output files to the workspace root +(should rarely be used), prefix the path with a "/". + +```yaml title="project.yml" {4-6} +tasks: + build: + command: 'webpack' + outputs: + # Relative from project root + - 'build/' +``` + +### `options` + +> `TaskOptionsConfig` + +The `options` field is an object of configurable options that can be used to modify the task and its +execution. The following fields can be provided, with merge related fields supporting all +[merge strategies](../concepts/task#merge-strategies). + +```yaml title="project.yml" {6-8} +tasks: + typecheck: + command: 'tsc' + args: '--noEmit' + options: + mergeArgs: 'replace' + runFromWorkspaceRoot: true +``` + +#### `mergeArgs` + +> `TaskMergeStrategy` + +The strategy to use when merging the [`args`](#args) list with an inherited task. Defaults to +"append". + +#### `mergeDeps` + +> `TaskMergeStrategy` + +The strategy to use when merging the [`deps`](#deps) list with an inherited task. Defaults to +"append". + +#### `mergeEnv` + +> `TaskMergeStrategy` + +The strategy to use when merging the [`env`](#env) map with an inherited task. Defaults to "append". + +#### `mergeInputs` + +> `TaskMergeStrategy` + +The strategy to use when merging the [`inputs`](#inputs) list with an inherited task. Defaults to +"append". + +#### `mergeOutputs` + +> `TaskMergeStrategy` + +The strategy to use when merging the [`outputs`](#outputs) list with an inherited task. Defaults to +"append". + +#### `retryCount` + +> `number` + +The number of attempts the task will retry execution before returning a failure. This is especially +useful for flaky tasks. Defaults to `0`. + +```yaml title="project.yml" {5} +tasks: + test: + # ... + options: + retryCount: 3 +``` + +#### `runInCI` + +> `boolean` + +Whether to run the task automatically in a CI (continuous integration) environment when affected by +touched files, typically through the `moon ci` command. Defaults to `true` unless the task name is +"start" or "serve", and is _always_ true when a task defines [`outputs`](#outputs). + +```yaml title="project.yml" {5} +tasks: + build: + # ... + options: + runInCI: false +``` + +#### `runFromWorkspaceRoot` + +> `boolean` + +Whether to use the workspace root as the working directory when executing a task. Defaults to +`false` and runs from the task's project root. + +```yaml title="project.yml" {5} +tasks: + typecheck: + # ... + options: + runFromWorkspaceRoot: true +``` + +### `type` + +The `type` field defines the type of command to run, where to locate its executable, and which tool +to execute it with. Accepts "node" or "system" and defaults to "node". + +```yaml title="project.yml" {4} +tasks: + env: + command: 'printenv' + type: 'system' +``` + +> This field exists because of our [toolchain](../concepts/toolchain), and moon ensuring the correct +> command is ran. + +## `workspace` + +Dictates how a project interacts with settings defined at the workspace-level. + +### `inheritedTasks` + +Provides a layer of control when inheriting tasks from [`.moon/project.yml`](./global-project). + +#### `exclude` + +The optional `exclude` setting permits a project to exclude specific tasks from being inherited. It +accepts a list of strings, where each string is the ID of a global task to exclude. + +```yaml title="project.yml" {4} +workspace: + inheritedTasks: + # Exclude the inherited `test` task for this project + exclude: ['test'] +``` + +> Exclusion is applied after inclusion and before renaming. + +#### `include` + +The optional `include` setting permits a project to _only_ include specific inherited tasks (works +like an allow/white list). It accepts a list of strings, where each string is the ID of a global +task to include. + +When this field is not defined, the project will inherit all tasks from the global project config. + +```yaml title="project.yml" {4,7-9} +workspace: + inheritedTasks: + # Include *no* tasks (works like a full exclude) + include: [] + + # Only include the `lint` and `test` tasks for this project + include: + - 'lint' + - 'test' +``` + +> Inclusion is applied before exclusion and renaming. + +#### `rename` + +The optional `rename` setting permits a project to rename the inherited task within the current +project. It accepts a map of strings, where the key is the original ID (found in the global project +config), and the value is the new ID to use. + +For example, say we have 2 tasks in the global project config called `buildPackage` and +`buildApplication`, but we only need 1, and since we're an application, we should omit and rename. + +```yaml title="project.yml" {4,5} +workspace: + inheritedTasks: + exclude: ['buildPackage'] + rename: + buildApplication: 'build' +``` + +> Renaming occurs after inclusion and exclusion. diff --git a/website/docs/config/workspace.mdx b/website/docs/config/workspace.mdx new file mode 100644 index 00000000000..8cc65ec194a --- /dev/null +++ b/website/docs/config/workspace.mdx @@ -0,0 +1,283 @@ +--- +title: .moon/workspace.yml +toc_max_heading_level: 6 +--- + +import RequiredLabel from '@site/src/components/Docs/RequiredLabel'; + +The `.moon/workspace.yml` file configures available projects and their locations, the toolchain, and +the workspace development environment. + +## `projects` + +> `Record` + +Defines the location of all [projects](../concepts/project) within the workspace. Each project +requires a unique ID as the map key, where this ID is used heavily on the command line and within +the project graph for uniquely identifying the project amongst all projects. The map value (known as +the project source) is a file system path to the project folder, relative from the workspace root, +and must be contained within the workspace boundary. + +```yaml title=".moon/workspace.yml" +projects: + admin: 'apps/admin' + apiClients: 'packages/api-clients' + designSystem: 'packages/design-system' + web: 'apps/web' +``` + +Unlike packages in the JavaScript ecosystem, a moon project _does not_ require a `package.json`, and +is not coupled to Yarn workspaces (or similar architectures). + +> **Why doesn't moon auto-detect projects?** moon _does not_ automatically detect projects using +> file system globs for the following reasons: +> +> - Depth-first scans are expensive, especially when the workspace continues to grow. +> - CI and other machines may inadvertently detect more projects because of left over artifacts. +> - Centralizing a manifest of projects allows for an easy review and approval process. + +## `node` + +> `NodeConfig` + +Defines the Node.js version and package manager to install within the toolchain, as moon _does not_ +use a Node.js binary found on the local machine. Managing the Node.js version within the toolchain +ensures a deterministic environment across any machine (whether a developer, CI, or production +machine). + +> This setting is optional and will default Node.js to the latest +> [active LTS version](https://nodejs.org/en/about/releases/) when not defined. + +### `version` + +> `string` + +Defines the explicit Node.js version to use. We require an explicit and semantic major, minor, and +patch version, to ensure the same environment is used across every machine. Ranges are _not_ +supported. + +```yaml title=".moon/workspace.yml" {2} +node: + version: '16.13.0' +``` + +> Version can be overridden with the `MOON_NODE_VERSION` environment variable. + +### `packageManager` + +> `npm | pnpm | yarn` + +Defines which package manager to utilize within the workspace. Supports `npm` (default), `pnpm`, or +`yarn`. + +```yaml title=".moon/workspace.yml" {2} +node: + packageManager: 'yarn' +``` + +### `npm`, `pnpm`, `yarn` + +> `PackageManagerConfig` + +Optional fields for defining package manager specific configuration. The chosen setting is dependent +on the value of [`node.packageManager`](#packagemanager). If these settings _are not defined_, the +latest version of the active package manager will be used (when applicable). + +#### `version` + +> `string` + +The `version` setting defines the explicit package manager version to use. We require an explicit +major, minor, and patch version, to ensure the same environment is used across every machine. + +```yaml title=".moon/workspace.yml" {4} +node: + packageManager: 'yarn' + yarn: + version: '3.1.0' +``` + +> Version can be overridden with the `MOON_NPM_VERSION`, `MOON_PNPM_VERSION`, or `MOON_YARN_VERSION` +> environment variables. + +### `addEnginesConstraint` + +> `boolean` + +Injects the currently configured [Node.js version](#version) as an `engines` constraint to the root +`package.json` field. Defaults to `true`. + +```yaml +node: + addEnginesConstraint: true +``` + +For example, say our Node.js version is "16.14.0", and when we execute a run process through the +`moon` binary, it will update the root `package.json` with the below. We pin a fixed version to +ensure other Node.js processes outside of our toolchain are utilizing the same version. + +```json title="package.json" +{ + // ... + "engines": { + "node": "16.14.0" + } +} +``` + +### `dedupeOnLockfileChange` + +> `boolean` + +Will dedupe dependencies after they have been installed, added, removing, or changed in any way, in +an effort to keep the workspace tree as clean and lean as possible. Defaults to `true`. + +```yaml title=".moon/workspace.yml" {2} +node: + dedupeOnLockfileChange: true +``` + +### `syncProjectWorkspaceDependencies` + +> `boolean` + +Will sync a project's [`dependsOn`](./project#dependson) setting as normal dependencies within the +project's `package.json`, using `workspace:*` or `*` version ranges (depending on what the package +manager supports). If a dependent project does not have a `package.json`, or if a dependency of the +same name has an explicit version already defined, the sync will be skipped. Defaults to `true`. + +```yaml title=".moon/workspace.yml" {2} +node: + syncProjectWorkspaceDependencies: true +``` + +A quick example on how this works. Given the following `dependsOn`: + +```yaml title="project.yml" +dependsOn: + - 'designSystem' + - 'reactHooks' +``` + +Would result in the following `dependencies` within a project's `package.json`. + +```json title="package.json" +{ + // ... + "dependencies": { + "@company/design-system": "workspace:*", + "@company/react-hooks": "workspace:*" + // ... + } +} +``` + +### `syncVersionManagerConfig` + +> `(none) | nodenv | nvm` + +Will sync the currently configured [Node.js version](#version) to a 3rd-party version manager's +config/rc file. Supports "nodenv" (syncs to `.node-version`), "nvm" (syncs to `.nvmrc`), or none +(default). + +```yaml title=".moon/workspace.yml" {2} +node: + syncVersionManagerConfig: 'nvm' +``` + +This is a special setting that ensure other Node.js processes outside of our toolchain are utilizing +the same version, which is a very common practice when managing dependencies. + +## `typescript` + +> `TypeScriptConfig` + +Dictates how moon interacts with and utilizes TypeScript within the workspace. + +### `projectConfigFileName` + +> `string` + +Defines the file name of the `tsconfig.json` found in the project root. We utilize this setting when +syncing project references between projects. Defaults to `tsconfig.json`. + +```yaml title=".moon/workspace.yml" {2} +typescript: + projectConfigFileName: 'tsconfig.build.json' +``` + +### `rootConfigFileName` + +> `string` + +Defines the file name of the `tsconfig.json` found in the workspace root. We utilize this setting +when syncing projects as references. Defaults to `tsconfig.json`. + +```yaml title=".moon/workspace.yml" {2} +typescript: + rootConfigFileName: 'tsconfig.projects.json' +``` + +### `syncProjectReferences` + +> `boolean` + +Will sync a project's [`dependsOn`](./project#dependson) setting as project references within that +project's `tsconfig.json`, and the workspace root `tsconfig.json`. Defaults to `true`. + +```yaml title=".moon/workspace.yml" {2} +typescript: + syncProjectReferences: true +``` + +A quick example on how this works. Given the following `dependsOn`: + +```yaml +dependsOn: + - 'designSystem' + - 'reactHooks' +``` + +Would result in the following `references` within both `tsconfig.json`s. + +```json title="tsconfig.json" +{ + // ... + "references": [ + // ... + { "path": "../../design-system" }, + { "path": "../../react-hooks" } + ] +} +``` + +## `vcs` + +> `VcsConfig` + +Configures the version control system to utilize within the workspace (and repository). A VCS is +required for determining touched (added, modified, etc) files, calculating file hashes, computing +affected files, and much more. + +### `manager` + +> `git | svn` + +Defines the VCS tool/binary that is being used for managing the repository. Accepts "git" (default) +or "svn" (experimental). + +```yaml title=".moon/workspace.yml" {2} +vcs: + manager: 'git' +``` + +### `defaultBranch` + +Defines the default upstream branch (master/main/trunk) in the repository for comparing differences +against. For git, this is typically "origin/master" (default) or "origin/main", and must include the +remote prefix (before /). For svn, this should always be "trunk". + +```yaml title=".moon/workspace.yml" {2} +vcs: + defaultBranch: 'origin/master' +``` diff --git a/website/docs/create-project.mdx b/website/docs/create-project.mdx new file mode 100644 index 00000000000..bf7539fc912 --- /dev/null +++ b/website/docs/create-project.mdx @@ -0,0 +1,186 @@ +--- +title: Create a project +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import HeaderLabel from '@site/src/components/Docs/HeaderLabel'; +import NextSteps from '@site/src/components/NextSteps'; + + + +With a [workspace](./setup-workspace), we can now house one or many [projects](./concepts/project), +with a project being an application, library, or tool. In the end, each project will have its own +build layer, personal tasks, and custom configuration. + +## Declaring a project in the workspace + +Although a project may exist in your repository, it's not accessible from moon until it's been +mapped in the [`projects`](./config/workspace#projects) setting found in +[`.moon/workspace.yml`](./config/workspace). When mapping a project, we require a unique identifier +for the project, and a project source location (path relative from the workspace root). + +Let's say we have a frontend web application called "client", and a backend Node.js application +called "server", our `projects` setting would look like the following. + +```yaml title=".moon/workspace.yml" +projects: + client: 'apps/client' + server: 'apps/server' +``` + +> For easier maintenance, we suggest alpha sorting the `projects` map! + +We can now run [`moon project client`](./commands/project) and +[`moon project server`](./commands/project) to display information about each project. If these +projects were not mapped, or were pointing to an invalid source, the command would throw an error. + +## Configuring a project + +A project can be configured in 1 of 2 ways: + +- Through the [`.moon/project.yml`](./config/global-project) config file, which defines file groups + and tasks that are inherited by _all_ projects within the workspace. Perfect for standardizing + common tasks like linting, type-checking, and code formatting. +- Through the [`project.yml`](./config/project) config file, found at the root of each project, + which defines files groups, tasks, dependencies, and more that are unique to that project. + +Both config files are optional, and can be used separately or together, the choice is yours! + +Now let's continue with our client and server example above. If we wanted to configure both +projects, and defined config that's also share between the 2, we could do something like the +following: + + + + +```yaml title="apps/client/project.yml" +tasks: + build: + command: 'webpack' + args: + - 'build' + - '--mode' + - 'production' + - '--entry' + - 'src/index.tsx' + - '--output-path' + - 'build' + inputs: + - 'src/**/*' + outputs: + - 'build' +``` + + + + +```yaml title="apps/server/project.yml" +tasks: + build: + command: 'babel' + args: + - 'src' + - '--out-dir' + - 'build' + inputs: + - 'src/**/*' + outputs: + - 'build' +``` + + + + +```yaml title=".moon/project.yml" +tasks: + format: + command: 'prettier' + args: '--write .' + inputs: + - 'src/**/*' + lint: + command: 'eslint' + args: '--no-error-on-unmatched-pattern .' + inputs: + - 'src/**/*' + test: + command: 'jest' + args: '--passWithNoTests .' + inputs: + - 'src/**/*' + - 'tests/**/*' + typecheck: + command: 'tsc' + args: '--build' + inputs: + - 'src/**/*' + - 'types/**/*' +``` + + + + +### Adding optional metadata + +When utilizing moon in a large monorepo or organization, ownership becomes very important. To combat +this problem, moon supports the [`project`](./config/project#project) field within a project's +[`project.yml`](./config/project) config. + +This field is _optional_ by default, but when defined it provides metadata about the project, +specifically around team ownership, which developers maintain the project, where to discuss it, and +more! + +```yaml title="project.yml" +project: + type: 'tool' + name: 'moon' + description: 'A repo management tool.' + channel: '#moon' + owner: 'infra.platform' + maintainers: ['miles.johnson'] +``` + +## Next steps + + + Configure .moon/workspace.yml further + + ), + url: './config/workspace', + }, + { + icon: 'project-config-global', + label: ( + + Configure .moon/project.yml further + + ), + url: './config/global-project', + }, + { + icon: 'project-config', + label: ( + + Configure project.yml further + + ), + url: './config/project', + }, + { icon: 'project', label: 'Learn about projects', url: './concepts/project' }, + ]} +/> diff --git a/website/docs/create-task.mdx b/website/docs/create-task.mdx new file mode 100644 index 00000000000..f21867aa271 --- /dev/null +++ b/website/docs/create-task.mdx @@ -0,0 +1,196 @@ +--- +title: Create a task +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import HeaderLabel from '@site/src/components/Docs/HeaderLabel'; +import NextSteps from '@site/src/components/NextSteps'; + + + +The primary feature of moon is a build system, and for it to operate in any capacity, it requires +tasks to run. In moon, a task is an npm binary or system command that is ran as a child process +within the context of a project (is the current working directory). Tasks are defined per project +with [`project.yml`](./config/project), or inherited by all projects with +[`.moon/project.yml`](./config/global-project). + +## Configuring a task + +Most, if not all projects, utilize the same core tasks: linting, testing, code formatting, and +type-checking. Because these are so universal, let's implement the type-checking task in +[`.moon/project.yml`](./config/global-project), which will be inherited by all projects. + +Begin by adding a `typecheck` field to the [`tasks`](./config/project#tasks) setting. This task will +use [TypeScript](https://www.typescriptlang.org/) and run `tsc` under the hood, since we defined the +[`command`](./config/project#command) setting. + +```yaml title=".moon/project.yml" {2,3} +tasks: + typecheck: + command: 'tsc' +``` + +By itself, this isn't doing much. So let's add some arguments with the +[`args`](./config/project#args) setting. We encourage everyone to use +[TypeScript project references](https://www.typescriptlang.org/docs/handbook/project-references.html) +for project boundaries and strict encapsulation, and as such, we'll use it below. + +```yaml title=".moon/project.yml" {4} +tasks: + typecheck: + command: 'tsc' + args: '--build --verbose' +``` + +With this, the task can be ran from the command line with +[`moon run :typecheck`](./commands/run)! This is tasks in its most simplest form, but +continue reading on how to take full advantage of our build system. + +### Inputs + +Our task above works, but isn't very efficient as it _always_ runs, regardless of what has changed +since the last time it has ran. This becomes problematic in continuous integration environments, not +just locally. + +To mitigate this problem, moon provides a system known as inputs, which are file paths and globs +that are used by the task when it's ran. moon will use and compare these inputs to calculate whether +to run, or to return the previous run state from the cache. + +If you're a bit confused, let's demonstrate this by expanding the task with the +[`inputs`](./config/project#inputs) setting. Since this is TypeScript, we expect a `tsconfig.json` +to exist in the project, and probably in the workspace root too. + +```yaml title=".moon/project.yml" {5-11} +tasks: + typecheck: + command: 'tsc' + args: '--build --verbose' + inputs: + - 'src/**/*' + - 'tests/**/*' + - 'types/**/*' + - 'tsconfig.json' + - '/tsconfig.*.json' + - '/tsconfig.json' +``` + +This list of inputs may look complicated, but they are merely run checks. For example, when moon +detects a change in... + +- Any files within the `src`, `tests`, and `types` folders, relative from the project's root. +- A `tsconfig.json` in the project's root. +- A `tsconfig.json` or any `tsconfig.*.json` in the workspace root (denoted by the leading `/`). + +...the task will be ran! If the change occurs _outside_ of the project or _outside_ the list of +inputs, the task will _not_ be ran. + +:::tip + +Inputs are a powerful feature that can be fine-tuned to your project's need. Be as granular or open +as you want, the choice is yours! + +::: + +### Outputs + +Outputs are the opposite of [inputs](#inputs), as they are files and folders that are created as a +result of running the task. With that being said, outputs are _optional_, as not all tasks require +them, and the ones that do are typically build related. + +Now why is declaring outputs important? For incremental builds and smart caching! When moon +encounters a build that has already been built, it hydrates all necessary outputs from the cache, +then immediately exits. No more waiting for long builds! + +Continuing our example, since we're using TypeScript project references and it generates declaration +files, we'll write them to a project local `dts` folder and mark it as an output. Let's expand our +task with the [`outputs`](./config/project#outputs) setting. + +```yaml title=".moon/project.yml" {12,13} +tasks: + typecheck: + command: 'tsc' + args: '--build --verbose' + inputs: + - 'src/**/*' + - 'tests/**/*' + - 'types/**/*' + - 'tsconfig.json' + - '/tsconfig.*.json' + - '/tsconfig.json' + outputs: + - 'dts' +``` + +## Using file groups + +Once you're familiar with configuring tasks, you may notice certain inputs being repeated +constantly, like source files, test files, and configuration. To reduce the amount of boilerplate +required, moon provides a feature known as file groups, which enables grouping of similar file types +within a project using file glob patterns or literal file paths. + +File groups are defined with the [`fileGroups`](./config/project#filegroups) setting, which maps a +list of file paths/globs to a group, like so. + +```yaml title=".moon/project.yml" +fileGroups: + configs: + - '*.config.js' + - 'tsconfig.json' + - '/tsconfig.*.json' + - '/tsconfig.json' + sources: + - 'src/**/*' + - 'types/**/*' + tests: + - 'tests/**/*' +``` + +We can then replace the inputs in our task above with these new file groups using a syntax known as +[tokens](./concepts/token), specifically the [`@globs`](./concepts/token#globs) and +[`@files`](./concepts/token#files) token functions. Tokens are an advanced feature, so please refer +to their documentation for more information! + +```yaml title=".moon/project.yml" {6-8} +tasks: + typecheck: + command: 'tsc' + args: '--build --verbose' + inputs: + - '@globs(sources)' + - '@globs(tests)' + - '@files(configs)' + outputs: + - 'dts' +``` + +With file groups (and tokens), you're able to reduce the amount of configuration required _and_ +encourage certain file structures for consuming projects! + +## Next steps + + + Configure .moon/project.yml further + + ), + url: './config/global-project', + }, + { + icon: 'project-config', + label: ( + + Configure project.yml further + + ), + url: './config/project', + }, + { icon: 'task', label: 'Learn about tasks', url: './concepts/task' }, + { icon: 'token', label: 'Learn about tokens', url: './concepts/token' }, + ]} +/> diff --git a/website/docs/guides/ci.mdx b/website/docs/guides/ci.mdx new file mode 100644 index 00000000000..175f370149f --- /dev/null +++ b/website/docs/guides/ci.mdx @@ -0,0 +1,223 @@ +--- +title: Continuous integration +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +All companies and projects rely on continuous integration (CI) to ensure high quality code and to +avoid regressions. Because this is such a critical piece of every developer's workflow, we wanted to +support it as a first-class feature within moon, and we do just that with the +[`moon ci`](../commands/ci) command. + +## How it works + +The `ci` command does all the heavy lifting necessary for effectively running jobs. It achieves this +by automatically running the following steps: + +- Determines touched files by comparing the current HEAD against a base. +- Determines all [targets](../concepts/target) that need to run based on touched files. +- Additionally runs affected [targets](../concepts/target) dependencies _and_ dependents. +- Generates an action and dependency graph. +- Installs the toolchain, Node.js, and npm dependencies. +- Runs all actions within the graph using a thread pool. +- Displays stats about all passing, failed, and invalid actions. + +## Configuring tasks + +By default, _all tasks_ run in CI, as you should always be building, linting, type-checking, +testing, so on and so forth. However, this isn't always true, so this can be disabled on a per-task +basis through the [`runInCI`](../config/project#runinci) option. + +```yaml +tasks: + dev: + command: webpack + args: server + options: + runInCI: false +``` + +:::caution + +This option _must_ be set to false for tasks that spawn a long running process, like HTTP or +development servers. To help mitigate this, tasks named `start` or `serve` are false by default. + +::: + +## Integrating + +Although moon has an [integrated toolchain](../concepts/toolchain), we still require Node.js and +dependencies to be installed _beforehand_, as moon is currently shipped as an +[npm package](https://www.npmjs.com/package/@moonrepo/cli). This is unfortunate and we're looking +into other distribution channels. + +With that being said, the following examples can be referenced for setting up moon and its CI +workflow in popular services. The examples assume a +[package script named `moon`](../install#adding-a-package-script) and are using Yarn 3, but feel +free to replace with your chosen package manager. + + + + +```yaml title=".github/workflows/ci.yml" +name: 'Pipeline' +on: + push: + branches: + - 'master' + pull_request: +jobs: + ci: + name: 'CI' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + - run: yarn install + - run: yarn moon ci +``` + + + + +```yaml title=".buildkite/pipeline.yml" +steps: + - label: 'CI' + commands: + - yarn install + - yarn moon ci +``` + + + + +```yaml title=".circleci/config.yml" +version: 2.1 +orbs: + node: 'circleci/node@5.0.2' +jobs: + ci: + docker: + - image: 'cimg/base:stable' + steps: + - checkout + - node/install: + install-yarn: true + node-version: '16.13' + - node/install-packages: + check-cache: always + pkg-manager: yarn-berry + - run: yarn moon ci +workflows: + pipeline: + jobs: + - ci +``` + + + + +```yaml title=".travis.yml" +language: node_js +node_js: + - 16 +cache: yarn +script: moon ci +``` + + + + +## Comparing revisions + +By default the command will compare the current HEAD against a base revision, which is typically the +configured [`vcs.defaultBranch`](../config/workspace#defaultbranch) (master, main, trunk, etc). Both +of these can be customized with the `--base` and `--head` options respectively. + +```shell +$ moon ci --base another-branch --head +``` + +## Parallelizing tasks + +If your CI environment supports sharding across multiple jobs, then you can utilize moon's built in +parallelism, by passing `--jobTotal` and `--job` options. The `--jobTotal` option is an integer of +the total number of jobs available, and `--job` is the current index (0 based) amongst the total. + +When these options are passed, moon will only run affected [targets](../concepts/target) based on +the current job slice. + + + + +GitHub Actions do not support native parallelism, but it can be emulated using it's matrix. + +```yaml title=".github/workflows/ci.yml" +# ... +jobs: + ci: + # ... + strategy: + matrix: + index: [0, 1] + steps: + # ... + - run: yarn moon ci --job ${{ matrix.index }} --jobTotal 2 +``` + +- [Documentation](https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs) + + + + +```yaml title=".buildkite/pipeline.yml" +# ... +steps: + - label: 'CI' + parallelism: 10 + commands: + # ... + - yarn moon ci --job $$BUILDKITE_PARALLEL_JOB --jobTotal $$BUILDKITE_PARALLEL_JOB_COUNT +``` + +- [Documentation](https://buildkite.com/docs/tutorials/parallel-builds#parallel-jobs) + + + + +```yaml title=".circleci/config.yml" +# ... +jobs: + ci: + # ... + parallelism: 10 + steps: + # ... + - run: yarn moon ci --job $CIRCLE_NODE_INDEX --jobTotal $CIRCLE_NODE_TOTAL +``` + +- [Documentation](https://circleci.com/docs/2.0/parallelism-faster-jobs/) + + + + +TravisCI does not support native parallelism, but it can be emulated using it's matrix. + +```yaml title=".travis.yml" +# ... +env: + global: + - TRAVIS_JOB_TOTAL=2 + jobs: + - TRAVIS_JOB_INDEX=0 + - TRAVIS_JOB_INDEX=1 +script: moon ci --job $TRAVIS_JOB_INDEX --jobTotal $TRAVIS_JOB_TOTAL +``` + +- [Documentation](https://docs.travis-ci.com/user/speeding-up-the-build/) + + + + +> Your CI environment may provide environment variables for these 2 values. diff --git a/website/docs/guides/open-source.mdx b/website/docs/guides/open-source.mdx new file mode 100644 index 00000000000..641ca033756 --- /dev/null +++ b/website/docs/guides/open-source.mdx @@ -0,0 +1,51 @@ +--- +title: Open source usage +--- + +Although moon was designed for large monorepos, it can also be used for open source projects, +especially when coupled with our [built-in continuous integration support](./ci). + +However, a pain point with moon is that it only supports a single Node.js version within its +[toolchain](../concepts/toolchain), but open source projects typically need to run checks against +multiple Node.js versions! To mitigate this problem, we built the +[`moonrepo/tool-version-action`](https://github.com/moonrepo/tool-version-action) GitHub Action, +which will override the Node.js version configured in the workspace. + +This action works best when paired with a matrix, as demonstrated below! + +```yaml title=".github/workflows/ci.yml" +name: 'Pipeline' +on: + push: + branches: + - 'master' + pull_request: +jobs: + ci: + name: 'CI' + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + node-version: [14, 16, 18] + steps: + # Checkout repository + - uses: actions/checkout@v3 + # Install Node.js so we can install dependencies + - uses: actions/setup-node@v3 + # Set Node.js version to use in moon + - uses: moonrepo/tool-version-action@v1 + with: + node: ${{ matrix.node-version }} + # Install dependencies + - run: yarn install --immutable + # Run moon and affected tasks + - run: yarn moon ci +``` + +:::info + +Currently, we only have a solution for GitHub actions, but the same mechanism can be applied to +other CI environments by setting the `MOON_NODE_VERSION` environment variable. + +::: diff --git a/website/docs/install.mdx b/website/docs/install.mdx new file mode 100644 index 00000000000..38c28771bba --- /dev/null +++ b/website/docs/install.mdx @@ -0,0 +1,251 @@ +--- +title: Install moon +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import HeaderLabel from '@site/src/components/Docs/HeaderLabel'; +import NextSteps from '@site/src/components/NextSteps'; + + + +The following guide can be used for adding moon to an existing repository (with incremental +adoption), or to a fresh repository. All that's required is [Node.js](https://nodejs.org/en/) >= +14.15.0 and a `package.json` at the root. + +## Choosing a package manager + +Before we install moon, we should briefly talk about Node.js package managers. To start, moon does +not replace a package manager, nor does it apply any "magic" for them to work differently. Instead, +moon builds upon a package manager to provide a robust build system, assumes a standard +`node_modules` layout and module resolution algorithm, and supports any of the following 3 package +managers. + +- [npm](https://docs.npmjs.com/) +- [pnpm](https://pnpm.io/) +- [Yarn](https://yarnpkg.com/) (v1, v2/3 in `node-modules` linker mode) + +We suggest Yarn 3 for its powerful workspaces implementation, extensive support for native binaries, +built-in package patching, and blazing speed, but feel free to choose the one that works best for +you! + +### Enabling workspaces + +moon was built with monorepos in mind, and as such, has first-class support for them through package +workspaces (but is not a requirement). To utilize workspaces, enable them for your chosen package +manager. + + + + +```json title="package.json" +{ + // ... + "workspaces": ["apps/*", "packages/*"] +} +``` + +```yaml title=".yarnrc.yml" +# ... +nodeLinker: 'node-modules' +``` + +- [Documentation](https://yarnpkg.com/features/workspaces) + + + + +```json title="package.json" +{ + // ... + "workspaces": ["apps/*", "packages/*"] +} +``` + +- [Documentation](https://classic.yarnpkg.com/en/docs/workspaces) + + + + +```json title="package.json" +{ + // ... + "workspaces": ["apps/*", "packages/*"] +} +``` + +- [Documentation](https://docs.npmjs.com/cli/v8/using-npm/workspaces) + + + + +```yaml title="pnpm-workspace.yaml" +packages: + - 'apps/*' + - 'packages/*' +``` + +- [Documentation](https://pnpm.io/workspaces) + + + + +## Installing the CLI + +The entirety of moon is packaged and shipped as a single binary through the `@moonrepo/cli` npm +package. Begin by installing this package at the root of the repository. + + + + +```shell +yarn add --dev @moonrepo/cli +``` + + + + +```shell +yarn add --dev @moonrepo/cli + +# If using workspaces +yarn add --dev -W @moonrepo/cli +``` + + + + +```bash +npm install --save-dev @moonrepo/cli +``` + + + + +```shell +pnpm add --dev @moonrepo/cli + +# If using workspaces +pnpm add --dev -W @moonrepo/cli +``` + + + + +### Accessing the `moon` binary + +We suggest _not_ installing the package globally, as adding an explicit development dependency with +a version range ensures all developers and machines are using the same version of moon. With that +being said, how should you access `moon`? If you'd like it to be accessible in a global-like +fashion, we suggest one of the following patterns: + +1. Update your `PATH` environment variable to include a path to the installed node module. This + would need to be added to every developers shell and other environments, like CI. + +```bash title="~/.bashrc" +export PATH="/path/to/repo/node_modules/@moonrepo/cli:$PATH" +``` + +```shell +moon --help +``` + +2. Symlink the binary to the root of the repository. This would require every developer to run the + binary from the repository root, which isn't the worst, but would work for other environments. + This can be automated through a `postinstall` script. + +```shell +ln -s ./node_modules/@moonrepo/cli/moon ./moon +``` + +```shell +./moon --help +``` + +3. Add the binary as a `package.json` script. Jump to the next section for more information. + +```shell +yarn run moon --help +``` + +### Adding a package script + +Another way to utilize the `moon` binary would be through a `package.json` script -- but this comes +with a cost -- which is the overhead of launching Node.js and your chosen package manager to execute +the Rust binary, _instead of_ executing the Rust binary directly. + +```json title="package.json" +{ + // ... + "scripts": { + // ... + "moon": "moon", + // For Yarn 2+ + "moon": "$(yarn bin moon)" + } +} +``` + +:::caution + +Yarn 2+ does not support executing Rust binaries through package scripts +([issue](https://github.com/yarnpkg/berry/issues/882)), so we must access the full binary path and +execute on that. + +::: + +With this script, moon can then be run with `npm run moon ...` (or `yarn run`, or `pnpm run`), but +do note that this pattern _will not_ work with package workspaces unless the script is ran from the +repository root. + +### Supported targets + +Because moon is written in Rust, we only support targets that are explicitly compiled for, which +are: + +- MacOS Intel (x64) +- Linux (x86_64, gnu) +- Windows (x64) + +## Initializing moon + +Now that we have the CLI installed, let's scaffold and initialize moon in a repository with the +[`moon init`](./commands/init) command. Be sure to [access the binary](#accessing-the-moon-binary) +as mentioned above. + +```shell +$ moon init +``` + +When executed, the following operations will be applied. + +- Creates a `.moon` folder with associated [`.moon/workspace.yml`](./config/workspace) and + [`.moon/project.yml`](./config/global-project) configuration files. +- Append necessary ignore patterns to the relative `.gitignore`. + +## Next steps + + diff --git a/website/docs/intro.md b/website/docs/intro.md new file mode 100644 index 00000000000..ad5e124eadc --- /dev/null +++ b/website/docs/intro.md @@ -0,0 +1,8 @@ +--- +slug: / +title: Introduction +--- + +moon is a repository *m*anagement, *o*rganization, *o*rchestration, and *n*otification tool for +JavaScript based projects, written in Rust. Many of the concepts within moon are heavily inspired +from Bazel and other popular build systems, but tailored for the JavaScript ecosystem. diff --git a/website/docs/run-task.mdx b/website/docs/run-task.mdx new file mode 100644 index 00000000000..c226b8c92d5 --- /dev/null +++ b/website/docs/run-task.mdx @@ -0,0 +1,102 @@ +--- +title: Run a task +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import HeaderLabel from '@site/src/components/Docs/HeaderLabel'; +import NextSteps from '@site/src/components/NextSteps'; + + + +Even though we've [created a task](./create-task), it's not useful unless we _run it_, which is done +with the [`moon run `](./commands/run) command. This command requires a single argument, a +[primary target](./concepts/target), which is the pairing of a project ID and task ID. In the +example below, our project is `app`, the task is `typecheck`, and the target is `app:typecheck`. + +```shell +$ moon run app:typecheck +``` + +When this command is ran, it will do the following: + +- Generate a directed acyclic graph, known as the action graph. +- Insert [`deps`](./config/project#deps) targets into the graph. +- Insert the primary target into the graph. +- Run all tasks in the graph in parallel and in topological order (the dependency chain). +- For each task, calculate [hashes](./concepts/cache) and either: + - On cache hit, exit early and return the last run. + - On cache miss, run the task and generate a new cache. + +## Running dependents + +moon will _always_ run upstream dependencies ([`deps`](./config/project#deps)) before running the +primary target, as their outputs may be required for the primary target to function correctly. + +However, if you're working on a project that is shared and consumed by other projects, you may want +to verify that downstream dependents have not been indirectly broken by any changes. This can be +achieved by passing the `--dependents` option, which will run dependent targets (of the same task +name) _after_ the primary target. + +```shell +$ moon run app:typecheck --dependents +``` + +## Running based on affected files only + +By default `moon run` will _always_ run the target, regardless if files have actually changed. +However, this is typically fine because of our [smart hashing & cache layer](./concepts/cache). With +that being said, if you'd like to _only_ run a target if files have changed, pass the `--affected` +flag. + +```shell +$ moon run app:typecheck --affected +``` + +Under the hood, we extract locally touched (created, modified, staged, etc) files from your +configured [VCS](./config/workspace#vcs), and exit early if no files intersect with the task's +[inputs](./config/project#inputs). + +### Using upstream changes + +If you'd like to determine affected files based on upstream changes, instead of local changes, pass +the `--upstream` flag. This will extract touched files by comparing the current `HEAD` against the +[`vcs.defaultBranch`](./config/workspace#defaultbranch). + +```shell +$ moon run app:typecheck --affected --upstream +``` + +### Filtering based on change status + +We can take this a step further by filtering down affected files based on a change status, using the +`--status` option. This option accepts the following values: `added`, `deleted`, `modified`, +`staged`, `unstaged`, `untracked`. If not provided, the option defaults to all. + +```shell +$ moon run app:typecheck --affected --status deleted +``` + +## Passing arguments to the underlying command + +If you'd like to pass arbitrary arguments to the underlying task command, in addition to the already +defined [`args`](./config/project#args), you can pass them after `--`. These arguments are _appended +as-is_. + +```shell +$ moon run app:typecheck -- --force +``` + +> The `--` delimeter and any arguments _must_ be defined last on the command line. + +## Next steps + +By this point, you should have a fully integrated and functional moon! Jump into [guides](./guides) +for advanced use cases or [concepts](./concepts) for a deeper understanding. + + diff --git a/website/docs/setup-workspace.mdx b/website/docs/setup-workspace.mdx new file mode 100644 index 00000000000..4e7e91cb907 --- /dev/null +++ b/website/docs/setup-workspace.mdx @@ -0,0 +1,124 @@ +--- +title: Setup workspace +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import HeaderLabel from '@site/src/components/Docs/HeaderLabel'; +import NextSteps from '@site/src/components/NextSteps'; + + + +Once moon has been initialized, we must setup the [workspace](./concepts/workspace), which is +denoted by the `.moon` folder and a relative `package.json` -- this is known as the workspace root. +The workspace is in charge of: + +- Integrating with a version control system. +- Defining configuration that applies to its entire tree. +- Housing [projects](./concepts/project) to build a project graph. +- Managing a [toolchain](./concepts/toolchain) that automatically installs Node.js and other tools. +- Running tasks and the build system. + +## Configuring Node.js + +The most important tool within moon and its toolchain is Node.js, as it's the backbone of a +JavaScript based repository. Let's now define the Node.js version we want to install in the +toolchain, enforce for developers, and to utilize when running `moon` commands, by updating +[`node.version`](./config/workspace#version) in [`.moon/workspace.yml`](./config/workspace). + +This setting requires an explicit semantic version and _does not_ support version ranges, so let's +set it to the latest version. We suggest _always_ using an +[Active LTS](https://nodejs.org/en/about/releases/) version. + +```yaml title=".moon/workspace.yml" +node: + version: '16.14.0' +``` + +Let's now run [`moon --logLevel debug setup`](./commands/setup) to verify that Node.js is downloaded +and installed correctly. Pretty cool right? + +## Configuring a package manager + +Even though Node.js is now installed, we need a package manager to install dependencies. During +installation, we talked about [choosing a package manager](./install#choosing-a-package-manager), so +let's use that choice here by defining [`node.packageManager`](./config/workspace#packagemanager). + +```yaml title=".moon/workspace.yml" +node: + version: '16.14.0' + packageManager: 'yarn' +``` + +By default moon will install the latest version of a package manager, but this isn't consistently +updated, so we suggest defining an explicit semantic version for the package manager as well, +through the [`node..version`](./config/workspace#version-1) setting. + +```yaml title=".moon/workspace.yml" +node: + version: '16.14.0' + packageManager: 'yarn' + yarn: + version: '3.2.0' +``` + +Once again, let's run [`moon --logLevel debug setup`](./commands/setup) to verify the package +manager is installed, and also take note that Node.js is _already_ installed. Based on the example +configuration above, we'll be switching from npm (the default) to yarn. + +## Configuring a version control system + +> moon defaults to `git` for a VCS, so feel free to skip this if you use git. + +moon requires a version control system (VCS) to be present for functionality like file diffing, +hashing, and revision comparison. The VCS and its default branch can be configured through the +[`vcs`](./config/workspace#vcs) setting. + + + + +```yaml title=".moon/workspace.yml" +vcs: + manager: 'git' + defaultBranch: 'origin/master' +``` + + + + +```yaml title=".moon/workspace.yml" +vcs: + manager: 'svn' + defaultBranch: 'trunk' +``` + +> SVN support is experimental and may not work properly! + + + + +## Next steps + + + Configure .moon/workspace.yml further + + ), + url: './config/workspace', + }, + { icon: 'workspace', label: 'Learn about the workspace', url: './concepts/workspace' }, + { icon: 'toolchain', label: 'Learn about the toolchain', url: './concepts/toolchain' }, + ]} +/> diff --git a/website/docs/terminology.md b/website/docs/terminology.md new file mode 100644 index 00000000000..83c3d57054d --- /dev/null +++ b/website/docs/terminology.md @@ -0,0 +1,32 @@ +--- +title: Terminology +--- + +| Term | Description | +| :--------------------- | :----------------------------------------------------------------------------------------------------------------------------------------- | +| Affected | Touched by an explicit set of inputs or sources. | +| Cache | Files and outputs that are stored on the file system to provide incremental builds and increased performance. | +| CI | Continuous integration. An environment where tests, builds, lints, etc, are continuously ran on every pull/merge request. | +| Downstream | Dependents or consumers of the item in question. | +| Hash | A unique SHA256 identifier that represents the result of a ran task. | +| Hashing | The mechanism of generating a hash based on multiple sources: inputs, dependencies, configs, etc. | +| LTS | Long-term support. | +| Package manager | Installs and manages dependencies for a specific tool (`npm`), using a manifest file (`package.json`). | +| [Project][project] | An collection of source and test files, configurations, a manifest and dependencies, and much more. Exists within a [workspace][workspace] | +| Revision | In the context of a VCS: a branch, revision, commit, hash, or point in history. | +| [Target][target] | A label and reference to a task within the project, in the format of `project:task`. | +| [Task][task] | A command to run within the context of and configured in a [project][project]. | +| [Token][token] | A value within task configuration that is substituted at runtime. | +| Tool | A programming language or package manager within the [toolchain][toolchain]. | +| Touched | A file that has been created, modified, deleted, or changed in any way. | +| [Toolchain][toolchain] | Installs and manages tools within the [workspace][workspace]. | +| Upstream | Dependencies or producers of the item in question. | +| VCS | Version control system (like git or svn). | +| [Workspace][workspace] | Root of the moon installation, and houses one or many [projects][project]. _Also refers to package manager workspaces (like Yarn)._ | + +[project]: ./concepts/project +[target]: ./concepts/target +[task]: ./concepts/task +[token]: ./concepts/token +[toolchain]: ./concepts/toolchain +[workspace]: ./concepts/workspace diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js new file mode 100644 index 00000000000..3085d89d0e4 --- /dev/null +++ b/website/docusaurus.config.js @@ -0,0 +1,153 @@ +/* eslint-disable sort-keys */ +// @ts-check + +// const path = require('path'); +const prismTheme = require('./prism.config'); + +/** @type {import('@docusaurus/types').Config} */ +const config = { + title: 'moon', + tagline: 'A build system for the JavaScript ecosystem', + url: 'https://moonrepo.dev', + baseUrl: '/', + onBrokenLinks: 'throw', + onBrokenMarkdownLinks: 'warn', + favicon: 'img/favicon.svg', + organizationName: 'milesj', + projectName: 'moon', + + presets: [ + [ + 'classic', + /** @type {import('@docusaurus/preset-classic').Options} */ + ({ + docs: { + sidebarPath: require.resolve('./sidebars.js'), + editUrl: 'https://github.com/milesj/moon/tree/master/website', + }, + // blog: { + // showReadingTime: true, + // // Please change this to your repo. + // editUrl: + // 'https://github.com/milesj/moon/tree/master/website', + // }, + theme: { + customCss: [ + require.resolve('./src/css/theme.css'), + require.resolve('./src/css/custom.css'), + ], + }, + }), + ], + ], + + themeConfig: + /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ + ({ + navbar: { + // title: 'moon', + logo: { + alt: 'moon', + src: 'img/logo.svg', + }, + items: [ + { + type: 'doc', + docId: 'intro', + position: 'left', + label: 'Docs', + }, + // { + // to: '/blog', + // label: 'Blog', + // position: 'left', + // }, + // { + // to: 'api', + // label: 'Packages', + // position: 'left', + // }, + ], + }, + footer: { + style: 'dark', + links: [ + { + title: 'Learn', + items: [ + { + label: 'Documentation', + to: '/docs', + }, + // { + // label: 'Packages', + // to: '/api', + // }, + ], + }, + { + title: 'Ecosystem', + items: [ + { + label: 'Releases', + to: 'https://github.com/milesj/moon/releases', + }, + { + label: 'Discussions', + to: 'https://github.com/milesj/moon/discussions', + }, + ], + }, + { + title: 'Support', + items: [ + { + label: 'GitHub', + to: 'https://github.com/milesj/moon', + }, + { + label: 'Discord', + to: 'https://discord.gg/qCh9MEynv2', + }, + { + label: 'Twitter', + to: 'https://twitter.com/tothemoonrepo', + }, + ], + }, + ], + copyright: `Copyright © ${new Date().getFullYear()} moon.`, + }, + prism: { + theme: prismTheme, + darkTheme: prismTheme, + }, + }), + + plugins: [ + // [ + // 'docusaurus-plugin-typedoc-api', + // { + // projectRoot: path.join(__dirname, '..'), + // packages: ['packages/runtime'], + // minimal: true, + // readme: true, + // }, + // ], + function tailwind() { + return { + name: 'docusaurus-tailwindcss', + configurePostCss(postcssOptions) { + // eslint-disable-next-line import/no-extraneous-dependencies, node/no-unpublished-require + postcssOptions.plugins.push(require('tailwindcss')); + + return postcssOptions; + }, + }; + }, + ], + + clientModules: [require.resolve('./src/js/darkModeSyncer.ts')], +}; + +module.exports = config; diff --git a/website/package.json b/website/package.json new file mode 100644 index 00000000000..d61f14be500 --- /dev/null +++ b/website/package.json @@ -0,0 +1,50 @@ +{ + "name": "website", + "version": "0.0.0", + "private": true, + "scripts": { + "docusaurus": "docusaurus", + "start": "docusaurus start", + "build": "docusaurus build", + "swizzle": "docusaurus swizzle", + "deploy": "docusaurus deploy", + "clear": "docusaurus clear", + "serve": "docusaurus serve", + "write-translations": "docusaurus write-translations", + "write-heading-ids": "docusaurus write-heading-ids" + }, + "dependencies": { + "@docusaurus/core": "2.0.0-beta.20", + "@docusaurus/preset-classic": "2.0.0-beta.20", + "@docusaurus/theme-common": "2.0.0-beta.20", + "@fortawesome/fontawesome-svg-core": "^6.1.1", + "@fortawesome/free-brands-svg-icons": "^6.1.1", + "@fortawesome/pro-duotone-svg-icons": "^6.1.1", + "@fortawesome/pro-regular-svg-icons": "^6.1.1", + "@fortawesome/react-fontawesome": "^0.1.18", + "@mdx-js/react": "^1.6.22", + "@moonrepo/cli": "workspace:*", + "clsx": "^1.1.1", + "docusaurus-plugin-typedoc-api": "^1.11.0", + "prism-react-renderer": "^1.3.1", + "react": "^17.0.2", + "react-dom": "^17.0.2" + }, + "devDependencies": { + "@docusaurus/module-type-aliases": "2.0.0-beta.20", + "@tsconfig/docusaurus": "^1.0.5", + "tailwindcss": "^3.0.24" + }, + "browserslist": { + "production": [ + ">0.5%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/website/prism.config.js b/website/prism.config.js new file mode 100644 index 00000000000..3a72a0822ab --- /dev/null +++ b/website/prism.config.js @@ -0,0 +1,86 @@ +const tailwind = require('./tailwind.config'); + +const { colors } = tailwind.theme; + +module.exports = { + plain: { + backgroundColor: colors.slate['900'], + color: colors.gray['100'], + }, + styles: [ + { + types: ['changed'], + style: { + color: colors.yellow['100'], + }, + }, + { + types: ['deleted'], + style: { + color: colors.red['300'], + }, + }, + { + types: ['inserted'], + style: { + color: colors.green['300'], + }, + }, + { + types: ['comment'], + style: { + color: colors.gray['600'], + fontStyle: 'italic', + }, + }, + { + types: ['punctuation'], + style: { + color: colors.gray['300'], + }, + }, + { + types: ['constant'], + style: { + color: colors.red['200'], + }, + }, + { + types: ['string', 'url'], + style: { + color: colors.green['200'], + }, + }, + { + types: ['variable'], + style: { + color: colors.yellow['100'], + }, + }, + { + types: ['number', 'boolean'], + style: { + color: colors.teal['300'], + }, + }, + { + types: ['attr-name'], + style: { + color: colors.yellow['300'], + }, + }, + { + types: ['keyword', 'operator', 'property', 'namespace', 'tag', 'selector', 'doctype'], + style: { + color: colors.purple['300'], + }, + }, + { + types: ['builtin', 'char', 'constant', 'function', 'class-name'], + style: { + color: colors.pink['300'], + fontWeight: 'bold', + }, + }, + ], +}; diff --git a/website/project.yml b/website/project.yml new file mode 100644 index 00000000000..dc20cb7d038 --- /dev/null +++ b/website/project.yml @@ -0,0 +1,27 @@ +tasks: + build: + command: 'docusaurus' + args: 'build' + outputs: + - 'build' + start: + command: 'docusaurus' + args: 'start' + options: + runInCI: false + typecheck: + command: 'tsc' + args: + - '--build' + - '--pretty' + - '--verbose' + inputs: + - '@globs(sources)' + - '*.js' + - 'tsconfig.json' + +workspace: + inheritedTasks: + exclude: + - 'build' + - 'typecheck' diff --git a/website/sidebars.js b/website/sidebars.js new file mode 100644 index 00000000000..13fb69d785a --- /dev/null +++ b/website/sidebars.js @@ -0,0 +1,79 @@ +/* eslint-disable sort-keys */ +// @ts-check + +/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ +const sidebars = { + docs: [ + 'intro', + { + type: 'category', + label: 'Getting started', + items: ['install', 'setup-workspace', 'create-project', 'create-task', 'run-task'], + }, + { + type: 'category', + label: 'Guides', + items: ['guides/ci', 'guides/open-source'], + link: { + type: 'generated-index', + title: 'Guides', + slug: '/guides', + keywords: ['guides'], + }, + }, + { + type: 'category', + label: 'Concepts', + items: [ + 'concepts/workspace', + 'concepts/toolchain', + 'concepts/project', + 'concepts/task', + 'concepts/target', + 'concepts/token', + 'concepts/cache', + ], + link: { + type: 'generated-index', + title: 'Concepts', + slug: '/concepts', + keywords: ['concepts'], + }, + }, + { + type: 'category', + label: 'Config files', + items: ['config/workspace', 'config/global-project', 'config/project'], + link: { + type: 'generated-index', + title: 'Config files', + slug: '/config', + keywords: ['config'], + }, + }, + { + type: 'category', + label: 'Commands', + items: [ + 'commands/bin', + 'commands/ci', + 'commands/init', + 'commands/project', + 'commands/project-graph', + 'commands/run', + 'commands/setup', + 'commands/teardown', + ], + link: { + type: 'generated-index', + title: 'Commands', + slug: '/commands', + keywords: ['cli', 'commands'], + }, + }, + 'terminology', + ], +}; + +// eslint-disable-next-line import/no-commonjs +module.exports = sidebars; diff --git a/website/src/components/Docs/HeaderLabel.tsx b/website/src/components/Docs/HeaderLabel.tsx new file mode 100644 index 00000000000..14b915b8a5b --- /dev/null +++ b/website/src/components/Docs/HeaderLabel.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { faClock } from '@fortawesome/pro-regular-svg-icons'; +import Label, { LabelProps } from '../../ui/typography/Label'; + +export type HeaderLabelProps = Pick; + +export default function HeaderLabel({ text }: HeaderLabelProps) { + return ( +