foruiman 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +11 -3
- data/docs/ARCHITECTURE.md +7 -2
- data/docs/COMPATIBILITY.md +59 -11
- data/docs/RAILS.md +12 -1
- data/lib/foruiman/cli.rb +41 -20
- data/lib/foruiman/configuration.rb +101 -0
- data/lib/foruiman/engine.rb +19 -3
- data/lib/foruiman/env.rb +5 -2
- data/lib/foruiman/tui/renderer.rb +2 -1
- data/lib/foruiman/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 906d36f65025e56552898118d3a3e5fec8b81c6046f32a7df9c627f048a89aa6
|
|
4
|
+
data.tar.gz: af7c8f777be7302aca9bd3e5cb50a17cc9acd9e2c6036e5d5b8033779d0a492f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b24d2f3e374331fe3c054c7930f5f1e5cd1e9bcc12e85befa8aeaf7bf8c367b70ab67942c19ac8a3fed56930ce9236a3cf422121a84bc2043584356450035ae0
|
|
7
|
+
data.tar.gz: 2b78d3244f014e4cf2e4959a8bedb62b308fb04c38fc8326d151d015983feeb8373a346852f2e730360b4e31a10a591dc63e410990e861571351646201ac9026
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.3.0
|
|
6
|
+
|
|
7
|
+
- Add `.foreman` YAML defaults with explicit CLI overrides and validated options.
|
|
8
|
+
- Add `run` for one-off commands and named Procfile entries, preserving terminal
|
|
9
|
+
input, argument boundaries, signals, and exit status.
|
|
10
|
+
- Match Foreman's explicit environment-file replacement, comma-separated loading,
|
|
11
|
+
environment `PORT` fallback, and Procfile/root path handling. See the migration
|
|
12
|
+
notes for changes from 0.2.
|
|
13
|
+
- Add `-t` / `--timeout` and `--exit-on all|any|failure`. Automatic group shutdown
|
|
14
|
+
preserves the triggering exit code and cleans up descendants and pending restarts.
|
|
15
|
+
|
|
5
16
|
## 0.2.0
|
|
6
17
|
|
|
7
18
|
- Keep child stdin open so default watcher modes continue running.
|
data/README.md
CHANGED
|
@@ -38,6 +38,8 @@ Useful commands:
|
|
|
38
38
|
foruiman start -f Procfile.dev # use another Procfile
|
|
39
39
|
foruiman start web # run one process
|
|
40
40
|
foruiman start --no-tui # stream plain logs
|
|
41
|
+
foruiman run bin/rails console # one-off command with the app environment
|
|
42
|
+
foruiman start --exit-on any # stop the group when a process exits
|
|
41
43
|
foruiman check # validate without starting
|
|
42
44
|
foruiman --version
|
|
43
45
|
```
|
|
@@ -59,13 +61,19 @@ process tab, `s` starts it again.
|
|
|
59
61
|
| Option | Default | Purpose |
|
|
60
62
|
| --- | --- | --- |
|
|
61
63
|
| `-f`, `--procfile FILE` | `Procfile` | Procfile to run |
|
|
62
|
-
| `-d`, `--root DIR` |
|
|
63
|
-
| `-e`, `--env
|
|
64
|
+
| `-d`, `--root DIR` | Procfile directory | Working directory |
|
|
65
|
+
| `-e`, `--env FILES` | `.env` | Comma-separated files, loaded in order instead of `.env` |
|
|
64
66
|
| `--no-dotenv` | `.env` enabled | Skip `.env` |
|
|
65
|
-
| `-p`, `--port PORT` | `5000` | Base port; entries increment by 100 |
|
|
67
|
+
| `-p`, `--port PORT` | Environment `PORT`, then `5000` | Base port; entries increment by 100 |
|
|
68
|
+
| `-t`, `--timeout SECONDS` | `5` | Grace period before forced shutdown |
|
|
69
|
+
| `--exit-on all\|any\|failure` | `all` | Keep peers running, stop on any exit, or stop on failure |
|
|
66
70
|
| `--log-lines N` | `10000` | Retained records per process and in `all` |
|
|
67
71
|
| `--no-tui` | TUI when interactive | Force plain output |
|
|
68
72
|
|
|
73
|
+
Set defaults in a `.foreman` YAML file in the invocation directory; CLI flags
|
|
74
|
+
override them. See [compatibility and migration notes](docs/COMPATIBILITY.md) for
|
|
75
|
+
path rules, supported settings, and changes from 0.2.
|
|
76
|
+
|
|
69
77
|
## Keyboard
|
|
70
78
|
|
|
71
79
|
| Key | Action |
|
data/docs/ARCHITECTURE.md
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
`Env` retains its quoting rules and adds a non-mutating precedence merge. `Process`
|
|
5
5
|
wraps `/bin/sh -c` with a new process group, two output streams, and configurable stdin.
|
|
6
6
|
`CLI < Thor` validates the full configuration before starting the engine.
|
|
7
|
+
`Configuration` safely loads `.foreman` defaults, merges explicit CLI values,
|
|
8
|
+
resolves Foreman-compatible paths and environments, and validates numeric options.
|
|
9
|
+
`run` uses `exec` for one-off commands with direct terminal IO and exact exit status.
|
|
7
10
|
|
|
8
11
|
`Engine` owns registration, PID/group tracking, nonblocking pipes, child reaping,
|
|
9
12
|
and lifecycle transitions. A single caller thread drives all mutations. Signal
|
|
@@ -47,8 +50,10 @@ Call engine methods and event listeners on the driving thread; this is not a
|
|
|
47
50
|
cross-thread messaging API. `run` handles INT/TERM/HUP and restores existing
|
|
48
51
|
handlers. An embedding loop that drives `tick` directly owns its signal handling.
|
|
49
52
|
`close` disconnects observers so a failed renderer/output consumer cannot prevent
|
|
50
|
-
process cleanup. `term_timeout:`
|
|
51
|
-
|
|
53
|
+
process cleanup. `term_timeout:` controls the shutdown grace period (CLI `-t`,
|
|
54
|
+
default five seconds). `exit_on: :all` retains independent processes; `:any` or
|
|
55
|
+
`:failure` requests group shutdown after a qualifying natural exit and preserves
|
|
56
|
+
its exit code. Intentional stops/restarts do not trigger the policy. `state(name)` exposes lifecycle state for
|
|
52
57
|
rendering; callers should not mutate it.
|
|
53
58
|
|
|
54
59
|
Children inherit the configured input stream in plain and embedded use. Before
|
data/docs/COMPATIBILITY.md
CHANGED
|
@@ -1,26 +1,74 @@
|
|
|
1
1
|
# Intentional differences from Foreman
|
|
2
2
|
|
|
3
|
-
Foruiman
|
|
3
|
+
Foruiman supports common Foreman development workflows. It is not a drop-in
|
|
4
|
+
replacement for every Foreman feature.
|
|
4
5
|
|
|
5
6
|
| Area | Foruiman behavior |
|
|
6
7
|
| --- | --- |
|
|
7
|
-
| Identity | `foruiman` gem/executable, `Foruiman` namespace, version 0.
|
|
8
|
+
| Identity | `foruiman` gem/executable, `Foruiman` namespace, version 0.3.0 |
|
|
8
9
|
| Runtime | Ruby 3.2+, POSIX process groups; Linux CI |
|
|
9
|
-
| CLI | Thor-based `start [PROCESS]`, `check`, `version`, and `help` |
|
|
10
|
-
| Excluded features | No export, scaling/formation,
|
|
10
|
+
| CLI | Thor-based `start [PROCESS]`, `run COMMAND [ARGS...]`, `check`, `version`, and `help` |
|
|
11
|
+
| Excluded features | No export, scaling/formation, forced color, or timestamp toggle |
|
|
11
12
|
| Procfile validation | Reject malformed lines, duplicates, empty commands, and reserved `all`; report line numbers |
|
|
12
|
-
| Working directory |
|
|
13
|
-
| Environment |
|
|
14
|
-
| Ports |
|
|
13
|
+
| Working directory | Procfile directory unless `-d`; explicit `-f` and `-e` paths resolve from invocation |
|
|
14
|
+
| Environment | Explicit `-e file1,file2` replaces default `.env` loading; later files win; parent `ENV` stays untouched |
|
|
15
|
+
| Ports | `-p` or `.foreman` port, then loaded `PORT`, then 5000; plus 100 per original entry |
|
|
15
16
|
| Expansion | `/bin/sh -c` performs shell expansion; no Ruby string substitution |
|
|
16
17
|
| Instances | Exactly one instance per entry; `PS=name.1` |
|
|
17
|
-
| Failures | Independent
|
|
18
|
+
| Failures | Independent by default; `--exit-on any` stops on any natural exit, `failure` only on an unsuccessful exit |
|
|
18
19
|
| Restart | Stop only the affected group, wait for descendants and output, then replace |
|
|
19
|
-
| Shutdown | TERM, five
|
|
20
|
+
| Shutdown | TERM, configurable `-t` grace (default five seconds), KILL; track groups after leader exit; restore prior signal handlers |
|
|
20
21
|
| Output | Separate stdout/stderr metadata; bounded logs and live partial records |
|
|
21
22
|
| Terminal | Tabs, independent scroll/follow, restart, start/stop, and selected-process input controls |
|
|
22
|
-
| Plain exit |
|
|
23
|
-
| Interactive exit | Remain open after all processes exit;
|
|
23
|
+
| Plain exit | Default status 0/1; automatic group shutdown preserves its triggering exit code (128 + signal for a signal exit); explicit orderly shutdown returns 0 |
|
|
24
|
+
| Interactive exit | Remain open after all processes exit by default; automatic shutdown policies close the TUI after cleanup |
|
|
25
|
+
|
|
26
|
+
## Configuration and paths
|
|
27
|
+
|
|
28
|
+
Like Foreman, `.foreman` is read from the invocation directory. Use long option
|
|
29
|
+
names; CLI flags override YAML values. Supported keys are `procfile`, `root`, `env`,
|
|
30
|
+
`port`, `timeout`, `dotenv`, `tui`, `log-lines`, and `exit-on` (underscores also work).
|
|
31
|
+
Unsupported keys, including `formation`, produce an error instead of being ignored.
|
|
32
|
+
YAML object tags and aliases are not supported.
|
|
33
|
+
|
|
34
|
+
```yaml
|
|
35
|
+
procfile: Procfile.dev
|
|
36
|
+
port: 3000
|
|
37
|
+
timeout: 10
|
|
38
|
+
exit-on: any
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
An explicit `-f` path is relative to invocation, even when `-d` is supplied. Without
|
|
42
|
+
`-f`, the Procfile is `<root>/Procfile`. Without `-d`, child commands run in the
|
|
43
|
+
Procfile's directory. Default `.env` loading follows Foreman's CLI: use the explicit
|
|
44
|
+
`-d` directory when supplied, otherwise the invocation directory. An inferred root
|
|
45
|
+
from `-f` does not move the default `.env` lookup. Explicit `-e` paths always resolve
|
|
46
|
+
from invocation. `--no-dotenv` disables only default `.env` loading.
|
|
47
|
+
|
|
48
|
+
## One-off commands
|
|
49
|
+
|
|
50
|
+
`foruiman run bin/rails console` uses the same environment-file rules and passes
|
|
51
|
+
stdin, stdout, and stderr directly to the command. No Procfile is required. A
|
|
52
|
+
single argument matching a Procfile entry runs that entry's shell command.
|
|
53
|
+
Other commands retain their exact argument boundaries. For shell operators, use
|
|
54
|
+
`foruiman run sh -c 'command1 && command2'`. Put Foruiman options before the command;
|
|
55
|
+
options after the command belong to that command.
|
|
56
|
+
|
|
57
|
+
`run` replaces Foruiman with the command, preserving signals and exit status. It
|
|
58
|
+
does not generate per-process `PORT` or `PS` values or open the TUI.
|
|
59
|
+
|
|
60
|
+
## Migrating from 0.2
|
|
61
|
+
|
|
62
|
+
- `-e custom.env` now replaces `.env`. Use `-e .env,custom.env` to retain layering.
|
|
63
|
+
- Environment `PORT` now selects the base port. Use `-p 5000` to retain the old default.
|
|
64
|
+
- A nested `-f` changes the default child working directory. Add `-d .` to keep
|
|
65
|
+
invocation as the working directory. With `-d app`, an old `-f config/Procfile.dev`
|
|
66
|
+
becomes `-f app/config/Procfile.dev` when that file lives inside `app`.
|
|
67
|
+
- A `.foreman` file now supplies defaults. Remove unsupported keys before using it.
|
|
68
|
+
|
|
69
|
+
Use `--exit-on any` for Foreman's stop-on-first-exit behavior. Intentional TUI
|
|
70
|
+
stops and restarts do not trigger that policy. Exact Foreman log formatting,
|
|
71
|
+
USR1/USR2 forwarding, and its Ruby embedding API remain outside compatibility.
|
|
24
72
|
|
|
25
73
|
Full-screen child terminal applications and background daemonization are outside
|
|
26
74
|
this release. Remote process control, persistence across Foruiman sessions,
|
data/docs/RAILS.md
CHANGED
|
@@ -35,7 +35,18 @@ foruiman start -f Procfile.dev -p 3000
|
|
|
35
35
|
|
|
36
36
|
The second example assigns 3000 to `web`, 3100 to `worker`, and 3200 to `css`.
|
|
37
37
|
Only programs that consume their generated `PORT` use those values. The base port
|
|
38
|
-
|
|
38
|
+
comes from `-p` or `.foreman`, then `PORT` in the loaded environment, then 5000.
|
|
39
|
+
|
|
40
|
+
Run a Rails command using the same environment without starting the Procfile:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
foruiman run bin/rails console
|
|
44
|
+
foruiman run -e .env,.env.local bin/rails db:migrate
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Explicit `-e` files replace default `.env` loading, so include `.env` in the list
|
|
48
|
+
when layering local overrides. For Foreman-style group shutdown and a longer grace
|
|
49
|
+
period, use `foruiman start -f Procfile.dev --exit-on any -t 10`.
|
|
39
50
|
|
|
40
51
|
An optional `bin/dev`:
|
|
41
52
|
|
data/lib/foruiman/cli.rb
CHANGED
|
@@ -4,31 +4,38 @@ require "thor"
|
|
|
4
4
|
require "foruiman"
|
|
5
5
|
require_relative "plain"
|
|
6
6
|
require_relative "diagnostics"
|
|
7
|
+
require_relative "configuration"
|
|
7
8
|
|
|
8
|
-
# Foreman's Thor command structure
|
|
9
|
+
# Foreman's Thor command structure with an interactive supervisor.
|
|
9
10
|
class Foruiman::CLI < Thor
|
|
10
11
|
map ["-v", "--version"] => :version
|
|
11
12
|
default_task :start
|
|
12
13
|
check_unknown_options!
|
|
13
14
|
remove_command :tree
|
|
14
15
|
|
|
15
|
-
class_option :procfile, type: :string, aliases: "-f",
|
|
16
|
-
class_option :root, type: :string, aliases: "-d", desc: "Working directory (default:
|
|
17
|
-
class_option :env, type: :string, aliases: "-e", desc: "
|
|
18
|
-
class_option :dotenv, type: :boolean,
|
|
19
|
-
class_option :port, type: :string, aliases: "-p",
|
|
20
|
-
class_option :log_lines, type: :string,
|
|
16
|
+
class_option :procfile, type: :string, aliases: "-f", desc: "Procfile to read (default: Procfile)"
|
|
17
|
+
class_option :root, type: :string, aliases: "-d", desc: "Working directory (default: Procfile directory)"
|
|
18
|
+
class_option :env, type: :string, aliases: "-e", desc: "Comma-separated environment files instead of .env"
|
|
19
|
+
class_option :dotenv, type: :boolean, desc: "Load default .env when -e is absent (default: true)"
|
|
20
|
+
class_option :port, type: :string, aliases: "-p", desc: "Base port (default: PORT or 5000)"
|
|
21
|
+
class_option :log_lines, type: :string, desc: "Maximum records per process and in all (default: 10000)"
|
|
22
|
+
class_option :timeout, type: :string, aliases: "-t", desc: "Seconds before escalating TERM to KILL (default: 5)"
|
|
23
|
+
class_option :exit_on, type: :string, desc: "Shutdown policy: all, any, failure (default: all)"
|
|
24
|
+
|
|
25
|
+
def self.is_thor_reserved_word?(word, type) # rubocop:disable Naming/PredicatePrefix -- Thor's extension API
|
|
26
|
+
word == "run" ? false : super
|
|
27
|
+
end
|
|
21
28
|
|
|
22
29
|
def self.exit_on_failure?
|
|
23
30
|
true
|
|
24
31
|
end
|
|
25
32
|
|
|
26
33
|
desc "start [PROCESS]", "Run the Procfile with process tabs, or stream logs without a TTY"
|
|
27
|
-
method_option :tui, type: :boolean,
|
|
34
|
+
method_option :tui, type: :boolean, desc: "Use the terminal interface when stdin and stdout are TTYs (default: true)"
|
|
28
35
|
def start(process = nil)
|
|
29
36
|
engine = build_engine
|
|
30
37
|
engine.select(process) if process
|
|
31
|
-
interactive =
|
|
38
|
+
interactive = configuration.tui? && $stdin.tty? && $stdout.tty?
|
|
32
39
|
diagnostics = Foruiman::Diagnostics.new(interactive: interactive)
|
|
33
40
|
code = if interactive
|
|
34
41
|
require_relative "tui/application"
|
|
@@ -48,6 +55,24 @@ class Foruiman::CLI < Thor
|
|
|
48
55
|
diagnostics&.close
|
|
49
56
|
end
|
|
50
57
|
|
|
58
|
+
desc "run COMMAND [ARGS...]", "Run a command or Procfile entry with the application's environment"
|
|
59
|
+
stop_on_unknown_option! :run
|
|
60
|
+
def run(*args)
|
|
61
|
+
raise Foruiman::Error, "run requires a command" if args.empty?
|
|
62
|
+
|
|
63
|
+
config = configuration
|
|
64
|
+
env = config.environment
|
|
65
|
+
command = Foruiman::Procfile.new(config.procfile)[args.first] if args.size == 1 && File.file?(config.procfile)
|
|
66
|
+
if command
|
|
67
|
+
exec(env, "/bin/sh", "-c", command, chdir: config.root, unsetenv_others: true)
|
|
68
|
+
else
|
|
69
|
+
# Preserve argv, terminal input, signals and the command's exact exit code.
|
|
70
|
+
exec(env, [args.first, args.first], *args.drop(1), chdir: config.root, unsetenv_others: true)
|
|
71
|
+
end
|
|
72
|
+
rescue Foruiman::Error, SystemCallError => e
|
|
73
|
+
raise Thor::Error, e.message
|
|
74
|
+
end
|
|
75
|
+
|
|
51
76
|
desc "check", "Validate the Procfile, environment files, ports, and log capacity without starting processes"
|
|
52
77
|
def check
|
|
53
78
|
engine = build_engine
|
|
@@ -65,19 +90,15 @@ class Foruiman::CLI < Thor
|
|
|
65
90
|
|
|
66
91
|
private
|
|
67
92
|
|
|
68
|
-
def
|
|
69
|
-
|
|
70
|
-
raise Foruiman::Error, "#{name.to_s.tr('_', '-')} must be a positive integer" unless text.match?(/\A[0-9]+\z/)
|
|
71
|
-
|
|
72
|
-
text.to_i
|
|
93
|
+
def configuration
|
|
94
|
+
@configuration ||= Foruiman::Configuration.new(options)
|
|
73
95
|
end
|
|
74
96
|
|
|
75
97
|
def build_engine
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
port: integer_option(:port), log_lines: integer_option(:log_lines))
|
|
98
|
+
config = configuration
|
|
99
|
+
env = config.environment
|
|
100
|
+
Foruiman::Engine.new(procfile: config.procfile, root: config.root, env: env,
|
|
101
|
+
port: config.port(env), log_lines: config.log_lines,
|
|
102
|
+
term_timeout: config.timeout, exit_on: config.exit_on)
|
|
82
103
|
end
|
|
83
104
|
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
class Foruiman::Configuration
|
|
6
|
+
OPTIONS = %w[procfile root env dotenv port log_lines timeout exit_on tui].freeze
|
|
7
|
+
BOOLEAN_OPTIONS = %w[dotenv tui].freeze
|
|
8
|
+
PATH_OPTIONS = %w[procfile root env].freeze
|
|
9
|
+
|
|
10
|
+
def initialize(options, directory: Dir.pwd)
|
|
11
|
+
@directory = directory
|
|
12
|
+
@options = defaults.merge(options.to_h.transform_keys { |key| key.to_s.tr("-", "_") })
|
|
13
|
+
BOOLEAN_OPTIONS.each do |key|
|
|
14
|
+
next unless @options.key?(key)
|
|
15
|
+
next if [true, false].include?(@options[key])
|
|
16
|
+
|
|
17
|
+
raise Foruiman::Error, "#{key}: expected true or false"
|
|
18
|
+
end
|
|
19
|
+
PATH_OPTIONS.each do |key|
|
|
20
|
+
next unless @options.key?(key)
|
|
21
|
+
next if @options[key].is_a?(String) && !@options[key].empty?
|
|
22
|
+
|
|
23
|
+
raise Foruiman::Error, "#{key}: expected a nonempty path"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def procfile
|
|
28
|
+
# Foreman's explicit -f is relative to invocation, independently of -d.
|
|
29
|
+
File.expand_path(@options.fetch("procfile", File.join(@options.fetch("root", "."), "Procfile")), @directory)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def root
|
|
33
|
+
File.expand_path(@options.fetch("root", File.dirname(procfile)), @directory)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def environment
|
|
37
|
+
# Foreman loads the default environment before inferring a root from -f.
|
|
38
|
+
env_root = File.expand_path(@options.fetch("root", "."), @directory)
|
|
39
|
+
files = @options["env"]&.split(",", -1)&.map do |file|
|
|
40
|
+
raise Foruiman::Error, "env: expected comma-separated file paths" if file.empty?
|
|
41
|
+
|
|
42
|
+
File.expand_path(file, @directory)
|
|
43
|
+
end
|
|
44
|
+
Foruiman::Env.load(root: env_root, file: files, dotenv: @options.fetch("dotenv", true))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def port(env)
|
|
48
|
+
integer("port", @options.fetch("port") { env.fetch("PORT", "5000") })
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def log_lines
|
|
52
|
+
integer("log_lines", @options.fetch("log_lines", "10000"))
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def timeout
|
|
56
|
+
value = Float(@options.fetch("timeout", 5), exception: false)
|
|
57
|
+
return value if value&.finite? && value >= 0
|
|
58
|
+
|
|
59
|
+
raise Foruiman::Error, "timeout must be a finite nonnegative number"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def exit_on
|
|
63
|
+
value = @options.fetch("exit_on", "all")
|
|
64
|
+
return value.to_sym if %w[all any failure].include?(value)
|
|
65
|
+
|
|
66
|
+
raise Foruiman::Error, "exit-on must be all, any, or failure"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def tui?
|
|
70
|
+
@options.fetch("tui", true)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def integer(name, value)
|
|
76
|
+
text = value.to_s
|
|
77
|
+
unless text.match?(/\A[0-9]+\z/) && text.to_i.positive?
|
|
78
|
+
raise Foruiman::Error, "#{name.tr('_', '-')} must be a positive integer"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
text.to_i
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def defaults
|
|
85
|
+
path = File.join(@directory, ".foreman")
|
|
86
|
+
return {} unless File.file?(path)
|
|
87
|
+
|
|
88
|
+
values = YAML.safe_load_file(path, permitted_classes: [], permitted_symbols: [], aliases: false)
|
|
89
|
+
return {} if values.nil?
|
|
90
|
+
raise Foruiman::Error, "#{path}: expected a YAML mapping of option names to values" unless values.is_a?(Hash)
|
|
91
|
+
|
|
92
|
+
values.each_with_object({}) do |(key, value), result|
|
|
93
|
+
name = key.to_s.tr("-", "_")
|
|
94
|
+
raise Foruiman::Error, "#{path}: unsupported option #{key.inspect}" unless OPTIONS.include?(name)
|
|
95
|
+
|
|
96
|
+
result[name] = value
|
|
97
|
+
end
|
|
98
|
+
rescue Psych::Exception => e
|
|
99
|
+
raise Foruiman::Error, "#{path}: #{e.message}"
|
|
100
|
+
end
|
|
101
|
+
end
|
data/lib/foruiman/engine.rb
CHANGED
|
@@ -18,12 +18,16 @@ class Foruiman::Engine
|
|
|
18
18
|
keyword_init: true)
|
|
19
19
|
Event = Data.define(:type, :name, :pid, :status, :record, :message)
|
|
20
20
|
|
|
21
|
-
attr_reader :logs, :env, :processes, :root, :procfile_path
|
|
21
|
+
attr_reader :logs, :env, :processes, :root, :procfile_path, :term_timeout, :exit_on
|
|
22
22
|
|
|
23
23
|
def initialize(procfile: nil, root: Dir.pwd, env: ENV.to_h, input: $stdin, port: 5000, log_lines: 10_000,
|
|
24
|
-
term_timeout: TERM_TIMEOUT)
|
|
24
|
+
term_timeout: TERM_TIMEOUT, exit_on: :all)
|
|
25
25
|
raise Foruiman::Error, "port must be an integer in 1..65535" unless port.is_a?(Integer) && (1..65_535).cover?(port)
|
|
26
26
|
raise Foruiman::Error, "log-lines must be a positive integer" unless log_lines.is_a?(Integer) && log_lines.positive?
|
|
27
|
+
unless term_timeout.is_a?(Numeric) && term_timeout.real? && term_timeout.finite? && term_timeout >= 0
|
|
28
|
+
raise Foruiman::Error, "timeout must be a finite nonnegative number"
|
|
29
|
+
end
|
|
30
|
+
raise Foruiman::Error, "exit-on must be all, any, or failure" unless %i[all any failure].include?(exit_on)
|
|
27
31
|
|
|
28
32
|
@root = File.expand_path(root)
|
|
29
33
|
raise Foruiman::Error, "working directory does not exist: #{@root}" unless File.directory?(@root)
|
|
@@ -34,6 +38,7 @@ class Foruiman::Engine
|
|
|
34
38
|
@base_port = port
|
|
35
39
|
@log_lines = log_lines
|
|
36
40
|
@term_timeout = term_timeout
|
|
41
|
+
@exit_on = exit_on
|
|
37
42
|
@processes = []
|
|
38
43
|
@names = {}
|
|
39
44
|
@running = {}
|
|
@@ -208,7 +213,9 @@ class Foruiman::Engine
|
|
|
208
213
|
end
|
|
209
214
|
|
|
210
215
|
def exit_code
|
|
211
|
-
|
|
216
|
+
return 0 if @explicit_shutdown
|
|
217
|
+
|
|
218
|
+
@policy_exit_code || (@failed ? 1 : 0)
|
|
212
219
|
end
|
|
213
220
|
|
|
214
221
|
def run(keep_open: false)
|
|
@@ -282,6 +289,7 @@ class Foruiman::Engine
|
|
|
282
289
|
entry.restart_pending = false
|
|
283
290
|
@failed = true
|
|
284
291
|
lifecycle(entry, :failed, "failed to start: #{e.message}")
|
|
292
|
+
apply_exit_policy(1)
|
|
285
293
|
return
|
|
286
294
|
end
|
|
287
295
|
entry.pid = entry.pgid = pid
|
|
@@ -337,10 +345,18 @@ class Foruiman::Engine
|
|
|
337
345
|
@failed ||= !success && !transitioning
|
|
338
346
|
entry.status = success ? :exited : :failed unless transitioning
|
|
339
347
|
lifecycle(entry, :exited, termination_message_for(entry.exit_status))
|
|
348
|
+
apply_exit_policy(entry.exit_status.exitstatus || (128 + entry.exit_status.termsig)) unless transitioning
|
|
340
349
|
terminate(entry)
|
|
341
350
|
end
|
|
342
351
|
end
|
|
343
352
|
|
|
353
|
+
def apply_exit_policy(code)
|
|
354
|
+
return if @shutdown || exit_on == :all || (exit_on == :failure && code.zero?)
|
|
355
|
+
|
|
356
|
+
@policy_exit_code = code
|
|
357
|
+
shutdown(explicit: false)
|
|
358
|
+
end
|
|
359
|
+
|
|
344
360
|
def advance_groups
|
|
345
361
|
processes.each do |entry|
|
|
346
362
|
next unless entry.pgid
|
data/lib/foruiman/env.rb
CHANGED
|
@@ -28,8 +28,11 @@ class Foruiman::Env
|
|
|
28
28
|
def self.load(root: Dir.pwd, file: nil, dotenv: true, inherited: ENV.to_h)
|
|
29
29
|
env = inherited.dup
|
|
30
30
|
default = File.join(root, ".env")
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
if file
|
|
32
|
+
Array(file).each { |filename| env.merge!(new(filename).to_h) }
|
|
33
|
+
elsif dotenv && File.file?(default)
|
|
34
|
+
env.merge!(new(default).to_h)
|
|
35
|
+
end
|
|
33
36
|
env.transform_values { |value| value.dup.freeze }.freeze
|
|
34
37
|
end
|
|
35
38
|
end
|
|
@@ -256,7 +256,8 @@ module Foruiman::TUI
|
|
|
256
256
|
|
|
257
257
|
quit = @theme.paint(" q ", :accent, bold: true) + @theme.paint(" × quit ", :muted)
|
|
258
258
|
left = if engine.shutting_down?
|
|
259
|
-
|
|
259
|
+
seconds = format("%g", engine.term_timeout)
|
|
260
|
+
@theme.paint(" ◌ Stopping process groups · TERM → KILL after #{seconds}s", :amber)
|
|
260
261
|
elsif state.feedback
|
|
261
262
|
@theme.paint(" #{state.feedback}", :amber)
|
|
262
263
|
elsif state.help
|
data/lib/foruiman/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: foruiman
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Foruiman contributors
|
|
@@ -70,6 +70,7 @@ files:
|
|
|
70
70
|
- lib/foruiman.rb
|
|
71
71
|
- lib/foruiman/ansi.rb
|
|
72
72
|
- lib/foruiman/cli.rb
|
|
73
|
+
- lib/foruiman/configuration.rb
|
|
73
74
|
- lib/foruiman/diagnostics.rb
|
|
74
75
|
- lib/foruiman/engine.rb
|
|
75
76
|
- lib/foruiman/env.rb
|
|
@@ -112,7 +113,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
112
113
|
- !ruby/object:Gem::Version
|
|
113
114
|
version: '0'
|
|
114
115
|
requirements: []
|
|
115
|
-
rubygems_version: 4.0.
|
|
116
|
+
rubygems_version: 4.0.20
|
|
116
117
|
specification_version: 4
|
|
117
118
|
summary: A Procfile supervisor with process tabs, bounded logs, and isolated restarts
|
|
118
119
|
test_files: []
|