vagrant-git-hooks 0.2.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.
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "vagrant"
4
+ require "i18n"
5
+
6
+ require_relative "version"
7
+ require_relative "helpers"
8
+ require_relative "config"
9
+ require_relative "command"
10
+ require_relative "service"
11
+ require_relative "util/git"
12
+
13
+ module VagrantGitHooks
14
+ class Plugin < Vagrant.plugin("2")
15
+ name "vagrant-git-hooks"
16
+
17
+ description <<~DESC
18
+ Manage git hooks declaratively from your Vagrantfile. A husky replacement
19
+ that needs no Node/npm, only Vagrant: it installs hook scripts into the
20
+ repository's hooks directory with ownership markers and backups.
21
+ DESC
22
+
23
+ config(:git_hooks) do
24
+ Config
25
+ end
26
+
27
+ command("hooks") do
28
+ Command
29
+ end
30
+
31
+ %i[machine_action_up machine_action_provision machine_action_reload].each do |hook_name|
32
+ action_hook(:vgh_install, hook_name) do |hook|
33
+ hook.append(Action::Install)
34
+ end
35
+ end
36
+
37
+ action_hook(:vgh_uninstall, :machine_action_destroy) do |hook|
38
+ hook.prepend(Action::Uninstall)
39
+ end
40
+ end
41
+
42
+ module Action
43
+ class Install
44
+ def initialize(app, _env) = (@app = app)
45
+
46
+ def call(env)
47
+ UiHelpers.setup_i18n!
48
+ cfg = env[:machine].config.git_hooks
49
+ UiHelpers.setup_locale_from_config!(cfg)
50
+ ui = env[:ui]
51
+ root = env[:machine].env.root_path.to_s
52
+
53
+ run(cfg, ui, root) if cfg.install_on_up
54
+ rescue StandardError => e
55
+ UiHelpers.error(env[:ui], "VGH: #{e.message}")
56
+ ensure
57
+ @app.call(env)
58
+ end
59
+
60
+ private
61
+
62
+ def run(cfg, ui, root)
63
+ unless Util::Git.available? && Util::Git.repo?(root)
64
+ UiHelpers.warn(ui, "#{UiHelpers.e(:warning)} #{UiHelpers.t("messages.not_a_repo")}")
65
+ return
66
+ end
67
+
68
+ res = Service.install!(root: root, cfg: cfg, ui: ui)
69
+ if res[:installed].empty? && res[:updated].empty?
70
+ UiHelpers.say(ui, "#{UiHelpers.e(:info)} #{UiHelpers.t("messages.no_hooks")}")
71
+ return
72
+ end
73
+
74
+ UiHelpers.say(ui, "#{UiHelpers.e(:success)} #{UiHelpers.t("messages.installed",
75
+ count: res[:installed].size + res[:updated].size)}")
76
+ return if res[:backed_up].empty?
77
+
78
+ UiHelpers.say(ui, "#{UiHelpers.e(:info)} #{UiHelpers.t("messages.backed_up",
79
+ list: res[:backed_up].join(", "))}")
80
+ end
81
+ end
82
+
83
+ class Uninstall
84
+ def initialize(app, _env) = (@app = app)
85
+
86
+ def call(env)
87
+ UiHelpers.setup_i18n!
88
+ cfg = env[:machine].config.git_hooks
89
+ UiHelpers.setup_locale_from_config!(cfg)
90
+ ui = env[:ui]
91
+ root = env[:machine].env.root_path.to_s
92
+
93
+ run(cfg, ui, root) if cfg.remove_on_destroy
94
+ rescue StandardError => e
95
+ UiHelpers.error(env[:ui], "VGH: #{e.message}")
96
+ ensure
97
+ @app.call(env)
98
+ end
99
+
100
+ private
101
+
102
+ def run(cfg, ui, root)
103
+ return unless Util::Git.available? && Util::Git.repo?(root)
104
+
105
+ res = Service.uninstall!(root: root, cfg: cfg, ui: ui)
106
+ return if res[:removed].empty? && res[:restored].empty?
107
+
108
+ UiHelpers.say(ui, "#{UiHelpers.e(:broom)} #{UiHelpers.t("messages.removed", count: res[:removed].size)}")
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "config"
4
+ require_relative "util/git"
5
+ require_relative "util/manifest"
6
+ require_relative "util/installer"
7
+
8
+ module VagrantGitHooks
9
+ module Service
10
+ module_function
11
+
12
+ # Builds installable hook entries from direct config and an optional source directory.
13
+ #
14
+ # @param cfg [VagrantGitHooks::Config] Plugin configuration.
15
+ # @param root [String, Pathname] Git repository root.
16
+ # @return [Array<Hash>] Hook entries consumed by the installer.
17
+ def entries_from_config(cfg, root)
18
+ entries = {}
19
+
20
+ (cfg.hooks || {}).each do |name, cmd|
21
+ entries[name.to_s] = { name: name.to_s, commands: cmd }
22
+ end
23
+
24
+ adopt_source(cfg, root, entries)
25
+ entries.values
26
+ end
27
+
28
+ def adopt_source(cfg, root, entries)
29
+ # Explicit Vagrantfile hooks win over hooks_source files with the same name.
30
+ src = cfg.hooks_source.to_s.strip
31
+ return if src.empty?
32
+
33
+ dir = File.absolute_path?(src) ? src : File.expand_path(src, root.to_s)
34
+ return unless File.directory?(dir)
35
+
36
+ Dir.children(dir).sort.each do |fname|
37
+ next unless Config::KNOWN_HOOKS.include?(fname)
38
+
39
+ path = File.join(dir, fname)
40
+ next unless File.file?(path)
41
+ next if entries.key?(fname)
42
+
43
+ entries[fname] = { name: fname, content: File.read(path) }
44
+ end
45
+ end
46
+
47
+ def installer_for(root, ui: nil)
48
+ Util::Installer.new(
49
+ hooks_dir: Util::Git.hooks_dir(root),
50
+ manifest: Util::Manifest.new(Util::Git.manifest_path(root)),
51
+ ui: ui
52
+ )
53
+ end
54
+
55
+ # Installs or updates hooks for a Git repository.
56
+ #
57
+ # @param root [String, Pathname] Git repository root.
58
+ # @param cfg [VagrantGitHooks::Config] Plugin configuration.
59
+ # @param ui [Object, nil] Optional Vagrant UI object.
60
+ # @param dry_run [Boolean] Whether to report planned changes without writing files.
61
+ # @return [Hash] Installation result.
62
+ def install!(root:, cfg:, ui: nil, dry_run: false)
63
+ installer_for(root, ui: ui).install(
64
+ entries_from_config(cfg, root),
65
+ fail_on_error: cfg.fail_on_error,
66
+ dry_run: dry_run
67
+ )
68
+ end
69
+
70
+ # Removes managed hooks and restores backed-up foreign hooks.
71
+ #
72
+ # @param root [String, Pathname] Git repository root.
73
+ # @param ui [Object, nil] Optional Vagrant UI object.
74
+ # @param dry_run [Boolean] Whether to report planned changes without writing files.
75
+ # @return [Hash] Uninstall result.
76
+ def uninstall!(root:, ui: nil, dry_run: false)
77
+ installer_for(root, ui: ui).uninstall(dry_run: dry_run)
78
+ end
79
+
80
+ # Reports hook installation status for configured and previously managed hooks.
81
+ #
82
+ # @param root [String, Pathname] Git repository root.
83
+ # @param cfg [VagrantGitHooks::Config] Plugin configuration.
84
+ # @return [Array<Hash>] Status rows keyed by hook name.
85
+ def status(root:, cfg:)
86
+ configured = entries_from_config(cfg, root).map { |e| e[:name].to_s }
87
+ installer_for(root).status(configured)
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require_relative "verbose"
5
+
6
+ module VagrantGitHooks
7
+ module Util
8
+ module Git
9
+ module_function
10
+
11
+ def available?
12
+ Verbose.log("git", "--version")
13
+ _out, _err, st = Open3.capture3("git", "--version")
14
+ st.success?
15
+ rescue StandardError
16
+ false
17
+ end
18
+
19
+ def repo?(root)
20
+ out, _err, st = capture(root, "rev-parse", "--is-inside-work-tree")
21
+ st.success? && out.strip == "true"
22
+ end
23
+
24
+ def toplevel(root)
25
+ out, _err, st = capture(root, "rev-parse", "--show-toplevel")
26
+ st.success? && !out.strip.empty? ? out.strip : nil
27
+ end
28
+
29
+ def hooks_dir(root)
30
+ resolve(root, "hooks") || File.join(toplevel(root) || root.to_s, ".git", "hooks")
31
+ end
32
+
33
+ def manifest_path(root)
34
+ resolve(root, "vagrant-git-hooks.json") ||
35
+ File.join(toplevel(root) || root.to_s, ".git", "vagrant-git-hooks.json")
36
+ end
37
+
38
+ def resolve(root, name)
39
+ out, _err, st = capture(root, "rev-parse", "--git-path", name)
40
+ return nil unless st.success?
41
+
42
+ path = out.strip
43
+ return nil if path.empty?
44
+
45
+ absolute?(path) ? path : File.expand_path(path, root.to_s)
46
+ end
47
+
48
+ def capture(root, *args)
49
+ Verbose.log("git", "-C", root.to_s, *args)
50
+ Open3.capture3("git", "-C", root.to_s, *args)
51
+ end
52
+
53
+ def absolute?(path)
54
+ File.absolute_path?(path)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "fileutils"
5
+ require "time"
6
+ require_relative "../hook_builder"
7
+
8
+ module VagrantGitHooks
9
+ module Util
10
+ class Installer
11
+ BACKUP_SUFFIX = ".bak"
12
+
13
+ def initialize(hooks_dir:, manifest:, ui: nil)
14
+ @hooks_dir = hooks_dir.to_s
15
+ @manifest = manifest
16
+ @ui = ui
17
+ end
18
+
19
+ # Installs or updates managed Git hooks.
20
+ #
21
+ # Existing foreign hooks are backed up once before replacement. Managed
22
+ # hooks are updated in place and tracked in the manifest by SHA-256 digest.
23
+ #
24
+ # @param entries [Array<Hash>] Hook definitions with `:name` and either `:commands` or `:content`.
25
+ # @param fail_on_error [Boolean] Whether generated hooks should include `set -e`.
26
+ # @param dry_run [Boolean] Whether to report planned changes without writing files.
27
+ # @return [Hash] Installation result with installed, updated, backed_up, and dry_run keys.
28
+ def install(entries, fail_on_error: true, dry_run: false)
29
+ FileUtils.mkdir_p(@hooks_dir) unless dry_run
30
+ data = @manifest.load
31
+ data["hooks_dir"] = @hooks_dir
32
+ managed = data["managed"]
33
+ result = { installed: [], updated: [], backed_up: [], dry_run: dry_run }
34
+
35
+ entries.each do |entry|
36
+ name = entry[:name].to_s
37
+ script = build(entry, fail_on_error)
38
+ path = File.join(@hooks_dir, name)
39
+ ours = File.exist?(path) && HookBuilder.managed?(safe_read(path))
40
+
41
+ backup = backup_if_foreign(path, name, ours, managed, result, dry_run)
42
+ write_hook(path, script) unless dry_run
43
+
44
+ (ours ? result[:updated] : result[:installed]) << name
45
+ managed[name] = {
46
+ "backup" => backup,
47
+ "sha256" => Digest::SHA256.hexdigest(script),
48
+ "installed_at" => now
49
+ }
50
+ end
51
+
52
+ @manifest.save(data) unless dry_run
53
+ result
54
+ end
55
+
56
+ # Removes managed hooks and restores backups recorded in the manifest.
57
+ #
58
+ # @param dry_run [Boolean] Whether to report planned changes without writing files.
59
+ # @return [Hash] Uninstall result with removed, restored, and dry_run keys.
60
+ def uninstall(dry_run: false)
61
+ data = @manifest.load
62
+ base = data["hooks_dir"] || @hooks_dir
63
+ result = { removed: [], restored: [], dry_run: dry_run }
64
+
65
+ data["managed"].each do |name, info|
66
+ path = File.join(base, name)
67
+
68
+ if File.exist?(path) && HookBuilder.managed?(safe_read(path))
69
+ File.delete(path) unless dry_run
70
+ result[:removed] << name
71
+ end
72
+
73
+ backup = info["backup"]
74
+ next unless backup && File.exist?(backup)
75
+
76
+ restore(backup, path) unless dry_run
77
+ result[:restored] << name
78
+ end
79
+
80
+ @manifest.clear unless dry_run
81
+ result
82
+ end
83
+
84
+ # Reports whether hooks are installed, modified, foreign, or missing.
85
+ #
86
+ # @param configured_names [Array<String>] Hook names configured in the current Vagrantfile.
87
+ # @return [Array<Hash>] Status rows with name, state, and backup fields.
88
+ def status(configured_names = [])
89
+ data = @manifest.load
90
+ base = data["hooks_dir"] || @hooks_dir
91
+ managed = data["managed"]
92
+ names = (managed.keys + configured_names.map(&:to_s)).uniq.sort
93
+
94
+ names.map do |name|
95
+ { name: name, state: state_of(File.join(base, name), managed[name]), backup: managed.dig(name, "backup") }
96
+ end
97
+ end
98
+
99
+ private
100
+
101
+ def build(entry, fail_on_error)
102
+ if entry[:content]
103
+ HookBuilder.adopt_script(name: entry[:name].to_s, content: entry[:content])
104
+ else
105
+ HookBuilder.script_for(name: entry[:name].to_s, commands: entry[:commands], fail_on_error: fail_on_error)
106
+ end
107
+ end
108
+
109
+ def backup_if_foreign(path, name, ours, managed, result, dry_run)
110
+ # Do not overwrite an existing backup: it is the user's original hook,
111
+ # not a rolling history of plugin-generated updates.
112
+ existing = managed.dig(name, "backup")
113
+ return existing unless File.exist?(path) && !ours && existing.nil?
114
+
115
+ backup = "#{path}#{BACKUP_SUFFIX}"
116
+ return backup if File.exist?(backup)
117
+
118
+ FileUtils.cp(path, backup) unless dry_run
119
+ result[:backed_up] << name
120
+ backup
121
+ end
122
+
123
+ def write_hook(path, script)
124
+ File.write(path, script)
125
+ File.chmod(0o755, path) unless Gem.win_platform?
126
+ end
127
+
128
+ def restore(backup, path)
129
+ FileUtils.mv(backup, path)
130
+ File.chmod(0o755, path) unless Gem.win_platform?
131
+ end
132
+
133
+ def state_of(path, info)
134
+ # A managed hook with a changed digest is still ours, but status reports
135
+ # it as modified so users can spot manual edits.
136
+ return "missing" unless File.exist?(path)
137
+ return "foreign" unless HookBuilder.managed?(safe_read(path))
138
+
139
+ if info && Digest::SHA256.hexdigest(safe_read(path)) != info["sha256"]
140
+ "modified"
141
+ else
142
+ "installed"
143
+ end
144
+ end
145
+
146
+ def safe_read(path)
147
+ File.read(path)
148
+ rescue StandardError
149
+ ""
150
+ end
151
+
152
+ def now
153
+ Time.now.utc.iso8601
154
+ rescue StandardError
155
+ Time.now.utc.to_s
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "fileutils"
5
+
6
+ module VagrantGitHooks
7
+ module Util
8
+ class Manifest
9
+ attr_reader :path
10
+
11
+ def initialize(path)
12
+ @path = path.to_s
13
+ end
14
+
15
+ # Loads the hook manifest from disk.
16
+ #
17
+ # Invalid or missing manifest data is treated as empty so hook commands can
18
+ # recover from interrupted runs.
19
+ #
20
+ # @return [Hash] Normalized manifest data.
21
+ def load
22
+ return empty unless File.exist?(@path)
23
+
24
+ data = JSON.parse(File.read(@path))
25
+ data.is_a?(Hash) ? normalize(data) : empty
26
+ rescue StandardError
27
+ empty
28
+ end
29
+
30
+ # Saves normalized hook manifest data.
31
+ #
32
+ # @param data [Hash] Manifest data.
33
+ # @return [Hash] Original data argument.
34
+ def save(data)
35
+ FileUtils.mkdir_p(File.dirname(@path))
36
+ File.write(@path, JSON.pretty_generate(normalize(data)))
37
+ data
38
+ end
39
+
40
+ def clear
41
+ File.delete(@path) if File.exist?(@path)
42
+ true
43
+ rescue StandardError
44
+ false
45
+ end
46
+
47
+ private
48
+
49
+ def empty
50
+ { "hooks_dir" => nil, "managed" => {} }
51
+ end
52
+
53
+ def normalize(data)
54
+ {
55
+ "hooks_dir" => data["hooks_dir"],
56
+ "managed" => (data["managed"].is_a?(Hash) ? data["managed"] : {})
57
+ }
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shellwords"
4
+
5
+ module VagrantGitHooks
6
+ module Util
7
+ module Verbose
8
+ module_function
9
+
10
+ def enabled?
11
+ ENV["VGH_VERBOSE"].to_s == "1"
12
+ end
13
+
14
+ def log(*args)
15
+ return unless enabled?
16
+
17
+ line = args.length == 1 && args.first.is_a?(String) ? args.first : args.map(&:to_s).shelljoin
18
+ warn("[VGH] #{line}")
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VagrantGitHooks
4
+ unless defined?(VERSION)
5
+ VERSION = begin
6
+ path = File.expand_path("VERSION", __dir__)
7
+ File.exist?(path) ? File.read(path).strip : "0.0.0"
8
+ rescue StandardError
9
+ "0.0.0"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "vagrant-git-hooks/version"
4
+ require_relative "vagrant-git-hooks/plugin"
data/locales/en.yml ADDED
@@ -0,0 +1,76 @@
1
+ en:
2
+ vgh:
3
+ emoji:
4
+ success: "✅"
5
+ info: "🔍"
6
+ ongoing: "🔁"
7
+ warning: "⚠️"
8
+ error: "❌"
9
+ version: "💾"
10
+ broom: "🧹"
11
+ question: "❓"
12
+ messages:
13
+ not_a_repo: "Not a git repository — skipping git hooks."
14
+ no_hooks: "No git hooks configured."
15
+ installed: "%{count} git hook(s) installed."
16
+ backed_up: "Backed up pre-existing hook(s): %{list}."
17
+ removed: "%{count} managed git hook(s) removed."
18
+ restored: "%{count} pre-existing hook(s) restored."
19
+ list_header: "Git hooks managed by the plugin:"
20
+ usage:
21
+ banner: "Usage: vagrant hooks <install|uninstall|list|run|version|help> [options]"
22
+ run: "Usage: vagrant hooks run <hook> [args...]"
23
+ errors:
24
+ unknown_command: "Unknown command. Try: vagrant hooks help."
25
+ no_machine: "No target machine found. Run this inside a Vagrant project or pass a VM name."
26
+ not_a_repo: "Not a git repository. Run this inside a git working tree."
27
+ cancelled: "Cancelled by user."
28
+ confirmation_required: "Confirmation required: pass --yes to proceed in --json mode."
29
+ hook_not_configured: "Hook '%{name}' is not configured in this Vagrantfile."
30
+ prompts:
31
+ uninstall: "This will remove all managed git hooks and restore backups. Continue? [y/N]"
32
+ log:
33
+ version_line: "v%{version}."
34
+ help:
35
+ general_title: "Usage: vagrant hooks <command> [args] [options]"
36
+ commands:
37
+ install: "hooks install Install configured hooks into .git/hooks"
38
+ uninstall: "hooks uninstall Remove managed hooks and restore backups"
39
+ list: "hooks list Show managed hooks and their state"
40
+ run: "hooks run <hook> [args] Run a configured hook on demand (host)"
41
+ version: "hooks version Print the plugin version"
42
+ help: "hooks help [TOPIC] Show this help or a subcommand's help"
43
+ topic:
44
+ install: |
45
+ 🔧 Help: vagrant hooks install
46
+ Usage:
47
+ vagrant hooks install [--dry-run] [--json]
48
+ Description:
49
+ Writes each configured hook into the repository hooks directory with a
50
+ managed header. A pre-existing, non-managed hook is backed up to
51
+ <hook>.bak before being replaced.
52
+ uninstall: |
53
+ 🧹 Help: vagrant hooks uninstall
54
+ Usage:
55
+ vagrant hooks uninstall [--yes] [--json]
56
+ Description:
57
+ Removes hooks managed by the plugin and restores any <hook>.bak backup.
58
+ list: |
59
+ 📋 Help: vagrant hooks list
60
+ Usage:
61
+ vagrant hooks list [--json]
62
+ Description:
63
+ Shows each hook with its state: installed, missing, modified, or foreign.
64
+ run: |
65
+ ▶️ Help: vagrant hooks run
66
+ Usage:
67
+ vagrant hooks run <hook> [args...]
68
+ Description:
69
+ Executes a configured hook on the host, useful for testing it without
70
+ making a commit.
71
+ version: |
72
+ 💾 Help: vagrant hooks version
73
+ Usage:
74
+ vagrant hooks version [--json]
75
+ Description:
76
+ Prints the plugin version.
data/locales/fr.yml ADDED
@@ -0,0 +1,75 @@
1
+ fr:
2
+ vgh:
3
+ emoji:
4
+ success: "✅"
5
+ info: "🔍"
6
+ ongoing: "🔁"
7
+ warning: "⚠️"
8
+ error: "❌"
9
+ version: "💾"
10
+ broom: "🧹"
11
+ question: "❓"
12
+ messages:
13
+ not_a_repo: "Pas un dépôt git — hooks ignorés."
14
+ no_hooks: "Aucun hook git configuré."
15
+ installed: "%{count} hook(s) git installé(s)."
16
+ backed_up: "Hook(s) préexistant(s) sauvegardé(s) : %{list}."
17
+ removed: "%{count} hook(s) git géré(s) supprimé(s)."
18
+ restored: "%{count} hook(s) préexistant(s) restauré(s)."
19
+ list_header: "Hooks git gérés par le plugin :"
20
+ usage:
21
+ banner: "Utilisation : vagrant hooks <install|uninstall|list|run|version|help> [options]"
22
+ run: "Utilisation : vagrant hooks run <hook> [args...]"
23
+ errors:
24
+ unknown_command: "Commande inconnue. Essayez : vagrant hooks help."
25
+ no_machine: "Aucune machine cible. Lancez ceci dans un projet Vagrant ou passez un nom de VM."
26
+ not_a_repo: "Pas un dépôt git. Lancez ceci dans une copie de travail git."
27
+ cancelled: "Annulé par l'utilisateur."
28
+ confirmation_required: "Confirmation requise : ajoutez --yes pour confirmer en mode --json."
29
+ hook_not_configured: "Le hook '%{name}' n'est pas configuré dans ce Vagrantfile."
30
+ prompts:
31
+ uninstall: "Ceci supprimera tous les hooks gérés et restaurera les sauvegardes. Continuer ? [y/N]"
32
+ log:
33
+ version_line: "v%{version}."
34
+ help:
35
+ general_title: "Utilisation : vagrant hooks <commande> [args] [options]"
36
+ commands:
37
+ install: "hooks install Installe les hooks configurés dans .git/hooks"
38
+ uninstall: "hooks uninstall Supprime les hooks gérés et restaure les sauvegardes"
39
+ list: "hooks list Affiche les hooks gérés et leur état"
40
+ run: "hooks run <hook> [args] Exécute un hook configuré à la demande (hôte)"
41
+ version: "hooks version Affiche la version du plugin"
42
+ help: "hooks help [SUJET] Affiche cette aide ou celle d'une sous-commande"
43
+ topic:
44
+ install: |
45
+ 🔧 Aide : vagrant hooks install
46
+ Utilisation :
47
+ vagrant hooks install [--dry-run] [--json]
48
+ Description :
49
+ Écrit chaque hook configuré dans le dossier de hooks du dépôt avec un
50
+ en-tête géré. Un hook préexistant non géré est sauvegardé en
51
+ <hook>.bak avant d'être remplacé.
52
+ uninstall: |
53
+ 🧹 Aide : vagrant hooks uninstall
54
+ Utilisation :
55
+ vagrant hooks uninstall [--yes] [--json]
56
+ Description :
57
+ Supprime les hooks gérés par le plugin et restaure toute sauvegarde <hook>.bak.
58
+ list: |
59
+ 📋 Aide : vagrant hooks list
60
+ Utilisation :
61
+ vagrant hooks list [--json]
62
+ Description :
63
+ Affiche chaque hook avec son état : installed, missing, modified ou foreign.
64
+ run: |
65
+ ▶️ Aide : vagrant hooks run
66
+ Utilisation :
67
+ vagrant hooks run <hook> [args...]
68
+ Description :
69
+ Exécute un hook configuré sur l'hôte, utile pour le tester sans commit.
70
+ version: |
71
+ 💾 Aide : vagrant hooks version
72
+ Utilisation :
73
+ vagrant hooks version [--json]
74
+ Description :
75
+ Affiche la version du plugin.