{"id":13393948,"url":"https://github.com/cortesi/modd","last_synced_at":"2025-05-14T18:04:51.859Z","repository":{"id":37444164,"uuid":"46884490","full_name":"cortesi/modd","owner":"cortesi","description":"A flexible developer tool that runs processes and responds to filesystem changes","archived":false,"fork":false,"pushed_at":"2023-05-03T00:31:00.000Z","size":12356,"stargazers_count":2855,"open_issues_count":48,"forks_count":130,"subscribers_count":39,"default_branch":"master","last_synced_at":"2025-04-20T10:57:00.458Z","etag":null,"topics":["developer-tools","livereload","process-manager"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cortesi.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2015-11-25T20:25:45.000Z","updated_at":"2025-04-17T17:23:52.000Z","dependencies_parsed_at":"2023-02-08T12:45:22.498Z","dependency_job_id":"fe19f285-1be7-487e-8e5c-3a5554822edf","html_url":"https://github.com/cortesi/modd","commit_stats":{"total_commits":255,"total_committers":17,"mean_commits":15.0,"dds":0.3019607843137255,"last_synced_commit":"ebf5f294cb932943ff188df9f0a7a0a900bea070"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cortesi%2Fmodd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cortesi%2Fmodd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cortesi%2Fmodd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cortesi%2Fmodd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cortesi","download_url":"https://codeload.github.com/cortesi/modd/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254198514,"owners_count":22030965,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["developer-tools","livereload","process-manager"],"created_at":"2024-07-30T17:01:03.389Z","updated_at":"2025-05-14T18:04:46.843Z","avatar_url":"https://github.com/cortesi.png","language":"Go","readme":"[![Travis Build Status](https://travis-ci.org/cortesi/modd.svg?branch=master)](https://travis-ci.org/cortesi/modd)\n\n\nModd is a developer tool that triggers commands and manages daemons in response\nto filesystem changes.\n\nIf you use modd, you should also look at\n[devd](https://github.com/cortesi/devd), a compact HTTP daemon for developers.\nDevd integrates with modd, allowing you to trigger in-browser livereload with\nmodd.\n\nThe repo contains a set of example *modd.conf* files that you can look at for a\nquick idea of what modd can do:\n\nExample                                      | Description\n-------------------------------------------- | -------\n[frontend.conf](./examples/frontend.conf)    | A front-end project with React + Browserify + Babel. Modd and devd replace many functions of Gulp/Grunt.\n[go.conf](./examples/go.conf)                | Live unit tests for Go.\n[python.conf](./examples/python.conf)        | Python + Redis, with devd managing livereload.\n\n\n\n# Install\n\nModd is a single binary with no external dependencies, released for OSX,\nWindows, Linux, FreeBSD, NetBSD and OpenBSD. Go to the [releases\npage](https://github.com/cortesi/modd/releases/latest), download the package for\nyour OS, and copy the binary to somewhere on your PATH.\n\nAlternatively, with Go 1.17+ installed, you can install `modd` directly using `go install`. Please note that CGO is required, so if you happen to have it disabled you will need to prepend the `CGO_ENABLED=1` environment variable.\n\n    $ go install github.com/cortesi/modd/cmd/modd@latest\n\n# Quick start\n\nPut this in a file called *modd.conf*:\n\n```\n**/*.go {\n    prep: go test @dirmods\n}\n```\n\nNow run modd like so:\n\n```\n$ modd\n```\n\nThe first time modd is run, it will run the tests of all Go modules. Whenever\nany file with the .go extension is modified, the \"go test\" command will be run\nonly on the enclosing module.\n\n\n# Details\n\nOn startup, modd looks for a file called *modd.conf* in the current directory.\nThis file has a simple but powerful syntax - one or more blocks of commands,\neach of which can be triggered on changes to files matching a set of file\npatterns. The *modd.conf* file is meant to be portable, and can safely be\nchecked into source repositories. Functionality that users will want to\ncustomize (like desktop notifications) is controlled through command-line\nflags.\n\nCommands have two flavors: **prep** commands that run and terminate (e.g.\ncompiling, running test suites or running linters), and **daemon** commands that\nrun and keep running (e.g databases or webservers). Daemons are sent a SIGHUP\n(by default) when their block is triggered, and are restarted if they ever exit.\n\nPrep commands are run in order of occurrence. If any prep command exits with an\nerror, execution of the current block is stopped immediately. If all prep\ncommands succeed, any daemons in the block are restarted, also in order of\noccurrence. If multiple blocks are triggered by the same set of changes, they\ntoo run in order, from top to bottom.\n\nHere's a modified version of the *modd.conf* file I use when hacking on devd.\nIt runs the test suite whenever a .go file changes, builds devd whenever a\nnon-test file is changed, and keeps a test instance running throughout.\n\n```\n**/*.go {\n    prep: go test @dirmods\n}\n\n# Exclude all test files of the form *_test.go\n**/*.go !**/*_test.go {\n    prep: go install ./cmd/devd\n    daemon +sigterm: devd -m ./tmp\n}\n```\n\nThe **@dirmods** variable expands to a properly escaped list of all directories\ncontaining changed files. When modd is first run, this includes all directories\ncontaining matching files. So, this means that modd will run all tests on\nstartup, and then subsequently run the tests only for the affected module\nwhenever there's a change. There's a corresponding **@mods** variable that\ncontains all changed files.\n\nNote the *+sigterm* flag to the daemon command. When devd receives a SIGHUP\n(the default signal sent by modd), it triggers a browser livereload, rather\nthan exiting. This is what you want when devd is being used to serve a web\nproject you're hacking on, but when developing devd _itself_, we actually want\nit to exit and restart to pick up changes. We therefore tell modd to send a\nSIGTERM to the daemon instead, which causes devd to exit and be restarted by\nmodd.\n\nBy default modd interprets commands using a [built-in POSIX-like\nshell](https://github.com/mvdan/sh). Some external shells are also supported,\nand can be used by setting `@shell` variable in your \"modd.conf\" file.\n\n\n# File watch patterns\n\nModd batches up changes until there is a lull in filesystem activity - this\nmeans that coherent processes like compilation and rendering that touch many\nfiles are likely to trigger commands only once. Patterns therefore match on a\nbatch of changed files - when the first match in a batch is seen, the block is\ntriggered.\n\nPatterns and the paths they match against are always in slash-delimited form,\neven on Windows. Paths are cleaned and normalised being matched, with redundant\ncomponents removed. If the path is within the current working directory, the\nnormalised path is relative to the current working directory, otherwise it is\nabsolute. One subtlety is that this means that a pattern like `./*.js` will\nnever match, because inbound paths will not have a leading `./` component - just\nuse `*.js` instead.\n\n\n## Quotes\n\nFile patterns can be naked or quoted strings. Quotes can be either single or\ndouble quotes, and the corresponding quote mark can be escaped with a backslash\nwithin the string:\n\n```\n\"**/foo\\\"bar\"\n```\n\n## Negation\n\nPatterns can be negated with a leading **!**. For quoted patterns, the\nexclamation mark goes outside of the quotes. So, this matches all files\nrecursively, bar those with a .html extension and those in the **docs**\ndirectory.\n\n```\n** !**/*.html !\"docs/**\"\n```\n\nNegations are applied after all positive patterns - that is, modd collects all\nfiles matching the positive patterns, then removes files matching the negation\npatterns.\n\n## Default ignore list\n\nCommon nuisance files like VCS directories, swap files, and so forth are\nignored by default. You can list the set of ignored patterns using the **-i**\nflag to the modd command. The default ignore patterns can be disabled using the\nspecial **+noignore** flag, like so:\n\n```\n.git/config +noignore {\n    prep: echo \"git config changed\"\n}\n```\n\n## Empty match pattern\n\nIf no match pattern is specified, prep commands run once only at startup, and\ndaemons are restarted if they exit, but won't ever be explicitly signalled to\nrestart by modd.\n\n```\n{\n    prep: echo hello\n}\n```\n\n## Symlinks\n\nModd does not implicitly traverse symlinks. To monitor a symlink, split the path\nspecification and the matching pattern, like this:\n\n```\nmydir/symlinkdir foo.* {\n    prep: echo changed\n}\n```\n\nBehind the scenes, we resolve the symlinked directory as if it was specified\ndirectly by the user. This means that if the symlink destination lies outside of\nthe current working directory, the resulting paths for matches, exclusions and\ncommands will be absolute.\n\n\n## Syntax\n\nFile patterns support the following syntax:\n\nTerm          | Meaning\n------------- | -------\n`*`           | any sequence of non-path-separators\n`**`          | any sequence of characters, including path separators\n`?`           | any single non-path-separator character\n`[class]`     | any single non-path-separator character against a class of characters\n`{alt1,...}`  | any of the comma-separated alternatives - to avoid conflict with the block specification, patterns with curly-braces should be enclosed in quotes\n\nAny character with a special meaning can be escaped with a backslash (`\\`).\nCharacter classes support the following:\n\nClass      | Meaning\n---------- | -------\n`[abc]`    | any character within the set\n`[a-z]`    | any character in the range\n`[^class]` | any character which does *not* match the class\n\n\n# Blocks\n\nEach file match pattern specification has an associated block, which is\nenclosed in curly brackets. Blocks contain commands and block-scoped options.\n\nSingle-line commands don't need to be quoted:\n\n```\nprep: echo \"I'm now rebuilding\" | tee /tmp/output\n```\n\nNewlines can be escaped with a backslash for multi-line commands:\n\n```\nprep: ls \\\n        -l \\\n        -a\n```\n\nYou can also enclose commands in single or double quotes, letting easily\nspecify compound, multi-statement commands. These can contain anything you'd\nnormally put in a shell script, and the same quoting and escaping conventions apply.\n\n```\nprep: \"\n    ls \\\n        -l \\\n        -a\n    echo \\\"hello again\\\"\n    echo \\\"hello yet again\\\"\n\"\n```\n\nWithin commands, the `@` character is treated specially, since it is the marker\nfor variable replacement. You can include a verbatim `@` symbol b escaping it\nwith a backslash, and backslashes preceding the `@` symbol can themselves be\nescaped recursively.\n\n```\n@foo = bar\n{\n    prep: echo \"@foo\"   # bar\n    prep: echo \"\\@foo\"  # @foo\n    prep: echo \"\\\\@foo\" # \\bar\n}\n```\n\n## Prep commands\n\nAll prep commands in a block are run in order before any daemons are restarted.\nIf any prep command exits with an error, execution stops.\n\nThe following variables are automatically generated for prep commands\n\nVariable      | Meaning\n------------- | -------\n@mods         | On first run, all files matching the block patterns. On subsequent change, a list of all modified files.\n@confdir      | The absolute path of the directory that contains the current modd config file.\n@dirmods      | On first run, all directories containing files matching the block patterns. On subsequent change, a list of all directories containing modified files.\n\nAll file names in variables are relative to the current directory, and\nshell-escaped for safety. All paths are in slash-delimited form on all\nplatforms.\n\nGiven a config file like this, modd will run *eslint* on all .js files when\nstarted, and then after that only run *eslint* on files if they change:\n\n```\n**/*.js {\n    prep: eslint @mods\n}\n```\n\nBy default, prep commands are executed on the initial run of modd. The\n`+onchange` option can be used to skip the initial run, and only execute when\nthere is a detected change.\n\n```\n*.go {\n\t# only trigger on file changes\n\tprep +onchange: go test\n}\n```\n\n\n## Daemon commands\n\nDaemons are executed on startup, and are restarted by modd whenever they exit.\nWhen a block containing a daemon command is triggered, modd sends a signal to\nthe daemon process group. If the signal causes the daemon to exit, it is\nimmediately restarted by modd - however, it's also common for daemons to do\nother useful things like reloading configuration in response to signals.\n\nThe default signal used is SIGHUP, but the signal can be controlled using\nmodifier flags, like so:\n\n```\ndaemon +sigterm: mydaemon --config ./foo.conf\n```\n\nThe following signals are supported: **sighup**, **sigterm**, **sigint**,\n**sigkill**, **sigquit**, **sigusr1**, **sigusr2**, **sigwinch**.\n\nSupport for signals on Windows is limited. The signal type is ignored, and all\ndaemons are stopped and restarted when a signal would normally be sent.\n\nThe following variables are automatically generated for prep commands\n\nVariable      | Meaning\n------------- | -------\n@confdir      | The absolute path of the directory that contains the current modd config file.\n\n\n## Controlling log headers\n\nModd outputs a short header on the terminal to show which command is responsible\nfor output. This header is calculated from the first non-whitespace line of the\ncommand - backslash escapes are removed from the end of the line, comment\ncharacters are removed from the beginning, and whitespace is stripped. Using the\nfact that the shell itself permits comments, you can completely control the log\ndisplay name.\n\n```\n{\n    # This will show as \"prep: mycommand\"\n    prep: \"\n        mycommand \\\n            --longoption 1 \\\n            --longoption 2\n    \"\n    # This will show as \"prep: daemon 1\"\n    prep: \"\n        # daemon 1\n        mycommand \\\n            --longoption 1 \\\n            --longoption 2\n    \"\n}\n```\n\n## Options\n\nThe only block option at the moment is **indir**, which controls the execution\ndirectory of a block. Modd will change to this directory before executing\ncommands and daemons, and change back to the previous directory afterwards.\n\nThe directory specification follows the same conventions as commands, and can\nbe enclosed in quotes to span multiple lines.\n\n```\n{\n    indir: ./my/directory\n    prep: ls\n}\n```\n\n\n# Variables\n\nVariables are declared as follows:\n\n```\n@variable = value\n```\n\nVariables can only be declared in the global scope (i.e. not inside blocks). All\nvalues are strings and follow the same semantics as commands - that is, they can\nhave escaped line endings, or be quoted strings. Variables are read once at\nstartup, and it is an error to re-declare a variable that already exists.\n\nYou can use variables in commands like so:\n\n```\n@dst = ./build/dst\n** {\n    prep: ls @dst\n}\n```\n\nThere is a special \"@shell\" variable that determines which shell is used to\nexecute commands. Valid values are `modd` (the default), `bash`, `sh` and\n`powershell`. This variable is set as follows:\n\n```\n@shell = bash\n```\n\nAvoid using the `@shell` variable if you can - using the built-in shell ensures\nthat `modd.conf` files remain portable across platforms.\n\n\n# Desktop Notifications\n\nWhen the **-n** flag is specified, modd sends anything sent to *stderr* from any\nprep command that exits abnormally to a desktop notifier. Since modd commands\nare shell scripts, you can redirect or manipulate output to entirely customise\nwhat gets sent to notifiers as needed.\n\nAt the moment, we support [Growl](http://growl.info/) on OSX, and\n[libnotify](https://launchpad.net/ubuntu/+source/libnotify) on Linux and other\nUnix systems.\n\n## Growl\n\nFor Growl to work, you will need Growl itself to be running, and have the\n**growlnotify** command installed. Growlnotify is an additional tool that you\ncan download from the official [Growl\nwebsite](http://growl.info/downloads.php).\n\n\n## Libnotify\n\nLibnotify is a general notification framework available on most Unix-like\nsystems. Modd uses the **notify-send** command to send notifications using\nlibnotify. You'll need to use your system package manager to install\n**libnotify**.\n\n\n# Colour output in process logs\n\nSome programs that have colourised output when run on the command-line don't\nemit colour when run under modd. Users might assume that modd is stripping the\ncolour from the command output, but that is not the case. Well-behaved terminal\nprograms check whether they are connected to a terminal, and if not, disable\ncolour codes in their own output. It is possible to trick a program into\nbelieving that a terminal is present through pseudo-terminal emulation, but this\nis complex and platform dependent and is not a good fit for a simple, reliable\ntool like modd.\n\nThis leaves users with two options:\n\n- Many tools that produce colour output also have a flag to force colour when no\n  terminal is detected, and many logging libraries with human-friendly output do\n  the same. The simplest solution is to work out how to force output and\n  explicitly specify this in your modd configuration.\n- There are platform-specific tools you can interpose between modd and the\n  subprocess to emulate a terminal. One example is\n  [unbuffer](https://linux.die.net/man/1/unbuffer) on Linux.\n\n\n# Development\n\nThe scripts used to build this package for distribution can be found\n[here](https://github.com/cortesi/godist).\n","funding_links":[],"categories":["Go","Uncategorized","File system"],"sub_categories":["Uncategorized"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcortesi%2Fmodd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcortesi%2Fmodd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcortesi%2Fmodd/lists"}