@sebastienrousseau/dotfiles 0.2.501 → 0.2.502
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.
- package/CHANGELOG.md +77 -0
- package/README.md +38 -8
- package/docs/COPYRIGHT +1 -1
- package/docs/index.md +2 -2
- package/docs/manual/00-introduction.md +1 -1
- package/docs/manual/03-reference/01-dot-cli.md +63 -45
- package/docs/manual/03-reference/02-config-files.md +2 -2
- package/docs/manual/03-reference/05-feature-flags.md +62 -3
- package/docs/manual/_toc.yml +1 -1
- package/docs/manual/command-index.md +12 -8
- package/docs/manual/index.md +66 -0
- package/docs/operations/COVERAGE.md +36 -2
- package/docs/operations/HARD_AUDIT_2026.md +631 -0
- package/docs/operations/REGISTRY.md +89 -0
- package/docs/operations/ROADMAP_2026.md +665 -0
- package/docs/operations/TRACEABILITY.md +3 -0
- package/docs/operations/VERSION_SYNC.md +4 -4
- package/docs/reference/POWERSHELL_PARITY.md +80 -0
- package/docs/registry.json +6 -0
- package/docs/security/CI_PINNING.md +113 -0
- package/docs/security/COMMIT_SIGNING.md +138 -0
- package/docs/security/DISCLOSURE.md +130 -0
- package/docs/security/KEY_ROTATION.md +81 -1
- package/docs/security/SCORECARD.md +74 -14
- package/docs/security/security-pubkey.asc +15 -0
- package/dot_config/fish/conf.d/direnv.fish +4 -0
- package/dot_config/fish/conf.d/mise-activate.fish +5 -0
- package/dot_config/git/hooks/executable_commit-msg +1 -1
- package/dot_config/shell/00-core-paths.sh.tmpl +8 -1
- package/dot_config/shell/README.md +1 -1
- package/dot_config/zsh/dot_zshrc.tmpl +57 -4
- package/dot_config/zsh/rc.d/30-options.zsh.tmpl +1 -1
- package/dot_local/bin/executable_dot +49 -9
- package/dot_local/bin/executable_dot-bootstrap +0 -1
- package/dot_local/bin/executable_dot-theme-sync +8 -8
- package/dot_local/bin/executable_tour +2 -2
- package/dot_local/share/man/man1/dot.1 +1 -1
- package/dot_local/share/zsh/completions/_dot +4 -0
- package/install.sh +44 -37
- package/package.json +1 -1
- package/scripts/ci/dot-cli-startup-bench.sh +126 -0
- package/scripts/ci/install-chezmoi-verified.sh +4 -1
- package/scripts/ci/lint-reusable-pins.sh +78 -0
- package/scripts/ci/run-coverage.sh +89 -0
- package/scripts/ci/windows-smoke-test.ps1 +136 -0
- package/scripts/diagnostics/doctor.sh +39 -9
- package/scripts/dot/commands/agent.sh +19 -22
- package/scripts/dot/commands/agents.sh +325 -0
- package/scripts/dot/commands/aliases.sh +10 -8
- package/scripts/dot/commands/core.sh +10 -4
- package/scripts/dot/commands/fleet.sh +278 -3
- package/scripts/dot/commands/init.sh +184 -0
- package/scripts/dot/commands/meta.sh +7 -4
- package/scripts/dot/commands/registry.sh +263 -0
- package/scripts/dot/commands/tools.sh +49 -1
- package/scripts/dot/lib/bento.sh +1 -1
- package/scripts/dot/lib/platform.sh +21 -8
- package/scripts/dot/lib/ui.sh +134 -2
- package/scripts/dot/lib/utils.sh +1 -1
- package/scripts/git-hooks/pre-commit-audit.sh +1 -1
- package/scripts/lib/secrets_provider.sh +32 -6
- package/scripts/ops/rollback.sh +14 -0
- package/scripts/security/check-disclosure-key-expiry.sh +110 -0
- package/scripts/security/lock-configs.sh +11 -2
- package/scripts/version-sync.sh +3 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Copyright (c) 2015-2026 Dotfiles. All rights reserved.
|
|
3
|
+
# shellcheck shell=bash
|
|
4
|
+
#
|
|
5
|
+
# scripts/dot/commands/init.sh
|
|
6
|
+
#
|
|
7
|
+
# `dot init <github-user>` — bootstrap a foreign dotfiles repo through
|
|
8
|
+
# this framework's harness. The §3 audit roadmap calls for an analogue
|
|
9
|
+
# to `chezmoi init <user>` that turns this tool from "one person's
|
|
10
|
+
# config" into a runtime for any dotfiles user.
|
|
11
|
+
#
|
|
12
|
+
# Behavior:
|
|
13
|
+
# dot init alice # clones github.com/alice/dotfiles via chezmoi,
|
|
14
|
+
# # applies through the dot harness
|
|
15
|
+
# dot init https://... # explicit URL
|
|
16
|
+
# dot init alice --dry-run # preview without writing
|
|
17
|
+
# dot init alice --no-apply # clone only; don't run chezmoi apply
|
|
18
|
+
#
|
|
19
|
+
# Safety:
|
|
20
|
+
# - Refuses to clobber an existing chezmoi source dir without --force.
|
|
21
|
+
# - Disclaims that the target's scripts will run with the user's
|
|
22
|
+
# privileges and prints the source URL for inspection.
|
|
23
|
+
# - Verifies the source URL is HTTPS (no plain git://) so MITM is harder.
|
|
24
|
+
|
|
25
|
+
set -euo pipefail
|
|
26
|
+
|
|
27
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
28
|
+
# shellcheck source=../lib/ui.sh disable=SC1091
|
|
29
|
+
source "$SCRIPT_DIR/../lib/ui.sh"
|
|
30
|
+
# shellcheck source=../lib/utils.sh disable=SC1091
|
|
31
|
+
source "$SCRIPT_DIR/../lib/utils.sh"
|
|
32
|
+
|
|
33
|
+
_init_resolve_url() {
|
|
34
|
+
local arg="$1"
|
|
35
|
+
case "$arg" in
|
|
36
|
+
http://*)
|
|
37
|
+
printf "dot init: refusing plain HTTP (use HTTPS or git+ssh)\n" >&2
|
|
38
|
+
return 2
|
|
39
|
+
;;
|
|
40
|
+
https://* | git@*:*)
|
|
41
|
+
# Trust full URLs as-is — these are the user's explicit intent.
|
|
42
|
+
printf "%s\n" "$arg"
|
|
43
|
+
;;
|
|
44
|
+
*/*)
|
|
45
|
+
# owner/repo shorthand: validate before constructing the URL so
|
|
46
|
+
# a crafted argument can't smuggle shell metacharacters into
|
|
47
|
+
# downstream tooling (chezmoi init, git clone).
|
|
48
|
+
if [[ ! "$arg" =~ ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ ]]; then
|
|
49
|
+
printf "dot init: invalid owner/repo (only [A-Za-z0-9._-]+/[A-Za-z0-9._-]+ allowed): %s\n" "$arg" >&2
|
|
50
|
+
return 2
|
|
51
|
+
fi
|
|
52
|
+
printf "https://github.com/%s.git\n" "$arg"
|
|
53
|
+
;;
|
|
54
|
+
*)
|
|
55
|
+
# Bare user shorthand: same character whitelist as the path
|
|
56
|
+
# segment of a GitHub URL.
|
|
57
|
+
if [[ ! "$arg" =~ ^[A-Za-z0-9._-]+$ ]]; then
|
|
58
|
+
printf "dot init: invalid user (only [A-Za-z0-9._-] allowed): %s\n" "$arg" >&2
|
|
59
|
+
return 2
|
|
60
|
+
fi
|
|
61
|
+
printf "https://github.com/%s/dotfiles.git\n" "$arg"
|
|
62
|
+
;;
|
|
63
|
+
esac
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
cmd_init() {
|
|
67
|
+
local user_arg="" dry_run=0 apply=1 force=0
|
|
68
|
+
while [[ $# -gt 0 ]]; do
|
|
69
|
+
case "$1" in
|
|
70
|
+
--dry-run | -n)
|
|
71
|
+
dry_run=1
|
|
72
|
+
shift
|
|
73
|
+
;;
|
|
74
|
+
--no-apply)
|
|
75
|
+
apply=0
|
|
76
|
+
shift
|
|
77
|
+
;;
|
|
78
|
+
--force | -f)
|
|
79
|
+
force=1
|
|
80
|
+
shift
|
|
81
|
+
;;
|
|
82
|
+
--help | -h)
|
|
83
|
+
cat <<EOF
|
|
84
|
+
Usage: dot init <github-user|owner/repo|url> [--dry-run] [--no-apply] [--force]
|
|
85
|
+
|
|
86
|
+
Bootstrap a foreign dotfiles repo through chezmoi + the dot harness.
|
|
87
|
+
|
|
88
|
+
Examples:
|
|
89
|
+
dot init alice # github.com/alice/dotfiles
|
|
90
|
+
dot init alice/configs # github.com/alice/configs
|
|
91
|
+
dot init https://example.com/repo.git
|
|
92
|
+
dot init alice --dry-run # preview only
|
|
93
|
+
dot init alice --no-apply # clone but don't apply
|
|
94
|
+
|
|
95
|
+
Flags:
|
|
96
|
+
--dry-run, -n Show resolved URL + target dir without cloning.
|
|
97
|
+
--no-apply Clone the source but skip 'chezmoi apply'.
|
|
98
|
+
--force, -f Overwrite an existing chezmoi source dir.
|
|
99
|
+
EOF
|
|
100
|
+
return 0
|
|
101
|
+
;;
|
|
102
|
+
--)
|
|
103
|
+
shift
|
|
104
|
+
break
|
|
105
|
+
;;
|
|
106
|
+
-*)
|
|
107
|
+
ui_err "Unknown flag" "$1"
|
|
108
|
+
return 1
|
|
109
|
+
;;
|
|
110
|
+
*)
|
|
111
|
+
if [[ -n "$user_arg" ]]; then
|
|
112
|
+
ui_err "Too many arguments" "$1"
|
|
113
|
+
return 1
|
|
114
|
+
fi
|
|
115
|
+
user_arg="$1"
|
|
116
|
+
shift
|
|
117
|
+
;;
|
|
118
|
+
esac
|
|
119
|
+
done
|
|
120
|
+
|
|
121
|
+
[[ -n "$user_arg" ]] || {
|
|
122
|
+
ui_err "init" "missing <user|repo|url>"
|
|
123
|
+
return 1
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
local url
|
|
127
|
+
url="$(_init_resolve_url "$user_arg")" || return $?
|
|
128
|
+
|
|
129
|
+
if ! command -v chezmoi >/dev/null 2>&1; then
|
|
130
|
+
ui_err "chezmoi" "not installed — run install.sh first"
|
|
131
|
+
return 127
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
local source_dir
|
|
135
|
+
source_dir="${CHEZMOI_SOURCE_DIR:-$(chezmoi source-path 2>/dev/null || echo "$HOME/.local/share/chezmoi")}"
|
|
136
|
+
|
|
137
|
+
ui_header "Bootstrap dotfiles from $user_arg"
|
|
138
|
+
ui_info "Source URL" "$url"
|
|
139
|
+
ui_info "Target dir" "$source_dir"
|
|
140
|
+
local _apply_label="no"
|
|
141
|
+
[[ "$apply" -eq 1 ]] && _apply_label="yes"
|
|
142
|
+
ui_info "Apply after?" "$_apply_label"
|
|
143
|
+
|
|
144
|
+
if [[ "$dry_run" -eq 1 ]]; then
|
|
145
|
+
ui_ok "Dry-run" "no changes made"
|
|
146
|
+
return 0
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
if [[ -e "$source_dir" ]] && [[ "$force" -ne 1 ]]; then
|
|
150
|
+
ui_err "Refusing" "$source_dir already exists; pass --force to overwrite"
|
|
151
|
+
return 1
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
ui_warn "Trust" "The target repo's scripts will run with your user privileges."
|
|
155
|
+
ui_warn "Trust" "Inspect $url before proceeding if you don't know the author."
|
|
156
|
+
if [[ -t 0 ]] && [[ "${DOTFILES_NONINTERACTIVE:-0}" != "1" ]]; then
|
|
157
|
+
read -r -p " Continue? [y/N] " resp
|
|
158
|
+
case "$resp" in
|
|
159
|
+
[yY] | [yY][eE][sS]) ;;
|
|
160
|
+
*)
|
|
161
|
+
ui_info "Init" "aborted by user"
|
|
162
|
+
return 1
|
|
163
|
+
;;
|
|
164
|
+
esac
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
local chezmoi_args=(init --source "$source_dir")
|
|
168
|
+
[[ "$apply" -eq 1 ]] && chezmoi_args+=(--apply)
|
|
169
|
+
chezmoi_args+=("$url")
|
|
170
|
+
|
|
171
|
+
ui_info "chezmoi" "${chezmoi_args[*]}"
|
|
172
|
+
if chezmoi "${chezmoi_args[@]}"; then
|
|
173
|
+
ui_ok "Init" "complete"
|
|
174
|
+
if [[ "$apply" -eq 1 ]]; then
|
|
175
|
+
ui_info "Hint" "run 'dot doctor' to validate the new environment"
|
|
176
|
+
else
|
|
177
|
+
ui_info "Hint" "run 'chezmoi diff' then 'chezmoi apply' when ready"
|
|
178
|
+
fi
|
|
179
|
+
else
|
|
180
|
+
local rc=$?
|
|
181
|
+
ui_err "Init" "chezmoi exited $rc"
|
|
182
|
+
return "$rc"
|
|
183
|
+
fi
|
|
184
|
+
}
|
|
@@ -61,10 +61,13 @@ cmd_prewarm() {
|
|
|
61
61
|
# Clear caches first
|
|
62
62
|
ui_info "Cache" "Clearing shell initialization caches"
|
|
63
63
|
local cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}"
|
|
64
|
-
rm -rf
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
# `find ... -delete` instead of `rm -rf $glob` so an unmatched literal
|
|
65
|
+
# pattern can't accidentally remove a real same-named directory, and
|
|
66
|
+
# so we don't pass `-r` on what should always be files.
|
|
67
|
+
[[ -d "$cache_dir/zsh" ]] && find "$cache_dir/zsh" -maxdepth 1 -type f \( -name '*-init.zsh' -o -name '*.zwc' \) -delete 2>/dev/null
|
|
68
|
+
[[ -d "$cache_dir/bash" ]] && find "$cache_dir/bash" -maxdepth 1 -type f -name '*-init.bash' -delete 2>/dev/null
|
|
69
|
+
[[ -d "$cache_dir/fish" ]] && find "$cache_dir/fish" -maxdepth 1 -type f -name '*-init.fish' -delete 2>/dev/null
|
|
70
|
+
[[ -d "$cache_dir/nushell" ]] && find "$cache_dir/nushell" -maxdepth 1 -type f -name '*.nu' -delete 2>/dev/null
|
|
68
71
|
ui_info "Cache" "Cleared. Regenerating..."
|
|
69
72
|
src_dir="$(resolve_source_dir)"
|
|
70
73
|
if [ -n "$src_dir" ] && [ -f "$src_dir/scripts/ops/prewarm.sh" ]; then
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Copyright (c) 2015-2026 Dotfiles. All rights reserved.
|
|
3
|
+
# shellcheck shell=bash
|
|
4
|
+
#
|
|
5
|
+
# scripts/dot/commands/registry.sh
|
|
6
|
+
#
|
|
7
|
+
# `dot registry` — initial scaffold for the dot module registry.
|
|
8
|
+
#
|
|
9
|
+
# §3 audit roadmap: ship a registry of reusable dotfile modules
|
|
10
|
+
# ("rust-dev-setup", "k8s-operator-laptop") to seed network effects.
|
|
11
|
+
# Hosted as a GitHub-Pages-indexed JSON file to keep ops cost near
|
|
12
|
+
# zero.
|
|
13
|
+
#
|
|
14
|
+
# Current state: SCAFFOLD ONLY. This file ships the CLI surface and
|
|
15
|
+
# the JSON contract (see _registry_default_url). The registry itself
|
|
16
|
+
# is empty; populating it is its own roadmap item.
|
|
17
|
+
#
|
|
18
|
+
# Subcommands:
|
|
19
|
+
# list Show modules in the configured registry
|
|
20
|
+
# search <q> Filter modules by keyword (name, description, tags)
|
|
21
|
+
# info <name> Print full metadata for a module
|
|
22
|
+
# install <name> Apply a module to the current workstation (stub —
|
|
23
|
+
# prints what would be installed; full implementation
|
|
24
|
+
# needs a sandboxed apply pipeline)
|
|
25
|
+
# url Show the active registry URL
|
|
26
|
+
# set-url <u> Override the registry URL (writes to user config)
|
|
27
|
+
#
|
|
28
|
+
# Registry JSON shape:
|
|
29
|
+
# {
|
|
30
|
+
# "version": 1,
|
|
31
|
+
# "updated": "2026-05-15T16:00:00Z",
|
|
32
|
+
# "modules": [
|
|
33
|
+
# { "name": "rust-dev-setup",
|
|
34
|
+
# "description": "Rust toolchain + cargo plugins + IDE config",
|
|
35
|
+
# "repo": "https://github.com/example/rust-dev-setup",
|
|
36
|
+
# "tags": ["rust", "dev", "language"],
|
|
37
|
+
# "maintainer": "alice@example.com",
|
|
38
|
+
# "version": "1.2.0",
|
|
39
|
+
# "sha256": "abc123..." }
|
|
40
|
+
# ]
|
|
41
|
+
# }
|
|
42
|
+
|
|
43
|
+
set -euo pipefail
|
|
44
|
+
|
|
45
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
46
|
+
# shellcheck source=../lib/ui.sh disable=SC1091
|
|
47
|
+
source "$SCRIPT_DIR/../lib/ui.sh"
|
|
48
|
+
# shellcheck source=../lib/utils.sh disable=SC1091
|
|
49
|
+
source "$SCRIPT_DIR/../lib/utils.sh"
|
|
50
|
+
|
|
51
|
+
_registry_default_url() {
|
|
52
|
+
printf '%s\n' "https://sebastienrousseau.github.io/dotfiles/registry.json"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
_registry_config_file() {
|
|
56
|
+
printf '%s/dotfiles/registry.toml\n' "${XDG_CONFIG_HOME:-$HOME/.config}"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
_registry_url() {
|
|
60
|
+
if [[ -n "${DOTFILES_REGISTRY_URL:-}" ]]; then
|
|
61
|
+
printf '%s\n' "$DOTFILES_REGISTRY_URL"
|
|
62
|
+
return
|
|
63
|
+
fi
|
|
64
|
+
local cfg
|
|
65
|
+
cfg="$(_registry_config_file)"
|
|
66
|
+
if [[ -f "$cfg" ]]; then
|
|
67
|
+
local u
|
|
68
|
+
u="$(awk -F'[ \t]*=[ \t]*' '/^url[ \t]*=/{gsub(/"/,"",$2); print $2; exit}' "$cfg")"
|
|
69
|
+
[[ -n "$u" ]] && {
|
|
70
|
+
printf '%s\n' "$u"
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
fi
|
|
74
|
+
_registry_default_url
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_registry_cache_dir() {
|
|
78
|
+
printf '%s/dotfiles/registry\n' "${XDG_CACHE_HOME:-$HOME/.cache}"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
_registry_fetch() {
|
|
82
|
+
local url cache_dir cache_file
|
|
83
|
+
url="$(_registry_url)"
|
|
84
|
+
cache_dir="$(_registry_cache_dir)"
|
|
85
|
+
cache_file="$cache_dir/index.json"
|
|
86
|
+
mkdir -p "$cache_dir"
|
|
87
|
+
# Refresh if older than 6h or missing.
|
|
88
|
+
local now mtime
|
|
89
|
+
now="$(date +%s)"
|
|
90
|
+
if [[ -s "$cache_file" ]]; then
|
|
91
|
+
if mtime="$(stat -f %m "$cache_file" 2>/dev/null || stat -c %Y "$cache_file" 2>/dev/null)"; then
|
|
92
|
+
if ((now - mtime < 21600)); then
|
|
93
|
+
printf '%s\n' "$cache_file"
|
|
94
|
+
return 0
|
|
95
|
+
fi
|
|
96
|
+
fi
|
|
97
|
+
fi
|
|
98
|
+
if ! command -v curl >/dev/null 2>&1; then
|
|
99
|
+
ui_err "registry" "curl not installed"
|
|
100
|
+
return 127
|
|
101
|
+
fi
|
|
102
|
+
local tmp
|
|
103
|
+
tmp="$(mktemp "${cache_file}.XXXXXX")"
|
|
104
|
+
if ! curl -fsSL --max-time 15 -o "$tmp" "$url"; then
|
|
105
|
+
rm -f "$tmp"
|
|
106
|
+
if [[ -s "$cache_file" ]]; then
|
|
107
|
+
ui_warn "registry" "fetch failed; using stale cache at $cache_file"
|
|
108
|
+
printf '%s\n' "$cache_file"
|
|
109
|
+
return 0
|
|
110
|
+
fi
|
|
111
|
+
ui_err "registry" "could not fetch $url"
|
|
112
|
+
return 1
|
|
113
|
+
fi
|
|
114
|
+
mv "$tmp" "$cache_file"
|
|
115
|
+
printf '%s\n' "$cache_file"
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
_registry_require_jq() {
|
|
119
|
+
command -v jq >/dev/null 2>&1 || {
|
|
120
|
+
ui_err "registry" "jq is required"
|
|
121
|
+
return 127
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
cmd_registry() {
|
|
126
|
+
local subcommand="${1:-list}"
|
|
127
|
+
shift || true
|
|
128
|
+
|
|
129
|
+
case "$subcommand" in
|
|
130
|
+
url)
|
|
131
|
+
printf '%s\n' "$(_registry_url)"
|
|
132
|
+
;;
|
|
133
|
+
set-url)
|
|
134
|
+
local new_url="${1:-}"
|
|
135
|
+
[[ -n "$new_url" ]] || {
|
|
136
|
+
ui_err "set-url" "missing URL"
|
|
137
|
+
return 1
|
|
138
|
+
}
|
|
139
|
+
# Refuse non-HTTPS schemes. The registry index is unsigned today,
|
|
140
|
+
# so HTTPS is the only transport that gives us cert-pinned
|
|
141
|
+
# integrity. The `file://` exemption is for local testing only
|
|
142
|
+
# (the bench-script and unit tests use it).
|
|
143
|
+
if [[ ! "$new_url" =~ ^(https://|file://) ]]; then
|
|
144
|
+
ui_err "set-url" "registry URL must use https:// (or file:// for local testing) — got: $new_url"
|
|
145
|
+
return 1
|
|
146
|
+
fi
|
|
147
|
+
local cfg
|
|
148
|
+
cfg="$(_registry_config_file)"
|
|
149
|
+
mkdir -p "$(dirname "$cfg")"
|
|
150
|
+
# Atomic write so a concurrent invocation can't read a half-
|
|
151
|
+
# written file. Explicit if/else (avoid SC2015 A && B || C).
|
|
152
|
+
local _tmp
|
|
153
|
+
_tmp="$(mktemp "${cfg}.XXXXXX")"
|
|
154
|
+
if printf 'url = "%s"\n' "$new_url" >"$_tmp"; then
|
|
155
|
+
if ! mv "$_tmp" "$cfg"; then
|
|
156
|
+
rm -f "$_tmp"
|
|
157
|
+
ui_err "set-url" "failed to commit $cfg"
|
|
158
|
+
return 1
|
|
159
|
+
fi
|
|
160
|
+
else
|
|
161
|
+
rm -f "$_tmp"
|
|
162
|
+
ui_err "set-url" "failed to write $cfg"
|
|
163
|
+
return 1
|
|
164
|
+
fi
|
|
165
|
+
ui_ok "registry" "set to $new_url ($cfg)"
|
|
166
|
+
;;
|
|
167
|
+
list)
|
|
168
|
+
_registry_require_jq || return $?
|
|
169
|
+
local index
|
|
170
|
+
index="$(_registry_fetch)" || return $?
|
|
171
|
+
ui_header "Registry modules"
|
|
172
|
+
ui_info "Source" "$(_registry_url)"
|
|
173
|
+
echo ""
|
|
174
|
+
if ! jq -e '.modules | length > 0' "$index" >/dev/null 2>&1; then
|
|
175
|
+
ui_warn "registry" "no modules published yet — see docs/operations/REGISTRY.md to contribute one"
|
|
176
|
+
return 0
|
|
177
|
+
fi
|
|
178
|
+
ui_table_begin "Module" "Version" "Description"
|
|
179
|
+
while IFS=$'\t' read -r name ver desc; do
|
|
180
|
+
ui_table_add "$name" "v$ver" "$desc"
|
|
181
|
+
done < <(jq -r '.modules[] | "\(.name)\t\(.version // "-")\t\(.description // "")"' "$index")
|
|
182
|
+
ui_table_end
|
|
183
|
+
;;
|
|
184
|
+
search)
|
|
185
|
+
_registry_require_jq || return $?
|
|
186
|
+
local q="${1:-}"
|
|
187
|
+
[[ -n "$q" ]] || {
|
|
188
|
+
ui_err "search" "missing query"
|
|
189
|
+
return 1
|
|
190
|
+
}
|
|
191
|
+
local index
|
|
192
|
+
index="$(_registry_fetch)" || return $?
|
|
193
|
+
ui_header "Registry search: $q"
|
|
194
|
+
echo ""
|
|
195
|
+
ui_table_begin "Module" "Version" "Description"
|
|
196
|
+
while IFS=$'\t' read -r name ver desc; do
|
|
197
|
+
ui_table_add "$name" "v$ver" "$desc"
|
|
198
|
+
done < <(jq -r --arg q "$q" '
|
|
199
|
+
.modules[]
|
|
200
|
+
| select(
|
|
201
|
+
(.name // "" | ascii_downcase | contains($q | ascii_downcase)) or
|
|
202
|
+
(.description // "" | ascii_downcase | contains($q | ascii_downcase)) or
|
|
203
|
+
((.tags // []) | map(ascii_downcase) | index($q | ascii_downcase))
|
|
204
|
+
)
|
|
205
|
+
| "\(.name)\t\(.version // "-")\t\(.description // "")"
|
|
206
|
+
' "$index")
|
|
207
|
+
ui_table_end
|
|
208
|
+
;;
|
|
209
|
+
info)
|
|
210
|
+
_registry_require_jq || return $?
|
|
211
|
+
local name="${1:-}"
|
|
212
|
+
[[ -n "$name" ]] || {
|
|
213
|
+
ui_err "info" "missing module name"
|
|
214
|
+
return 1
|
|
215
|
+
}
|
|
216
|
+
local index
|
|
217
|
+
index="$(_registry_fetch)" || return $?
|
|
218
|
+
local found
|
|
219
|
+
found="$(jq -r --arg n "$name" '.modules[] | select(.name == $n) | "OK"' "$index" 2>/dev/null)"
|
|
220
|
+
if [[ "$found" != "OK" ]]; then
|
|
221
|
+
ui_err "info" "module not found: $name"
|
|
222
|
+
return 1
|
|
223
|
+
fi
|
|
224
|
+
jq -r --arg n "$name" '.modules[] | select(.name == $n) | to_entries[] | "\(.key)\t\(.value | if type == "array" then join(", ") else tostring end)"' "$index" |
|
|
225
|
+
while IFS=$'\t' read -r key value; do
|
|
226
|
+
ui_ok "$key" "$value"
|
|
227
|
+
done
|
|
228
|
+
;;
|
|
229
|
+
install)
|
|
230
|
+
local name="${1:-}"
|
|
231
|
+
[[ -n "$name" ]] || {
|
|
232
|
+
ui_err "install" "missing module name"
|
|
233
|
+
return 1
|
|
234
|
+
}
|
|
235
|
+
ui_warn "install" "scaffold only — full module installer is a roadmap item"
|
|
236
|
+
ui_info "install" "would fetch module $name from registry and apply it as a chezmoi sub-source"
|
|
237
|
+
return 0
|
|
238
|
+
;;
|
|
239
|
+
--help | -h | help)
|
|
240
|
+
cat <<EOF
|
|
241
|
+
Usage: dot registry <subcommand>
|
|
242
|
+
|
|
243
|
+
Subcommands:
|
|
244
|
+
list List modules in the configured registry
|
|
245
|
+
search <q> Filter modules by keyword (name, description, tags)
|
|
246
|
+
info <name> Print metadata for a single module
|
|
247
|
+
install <name> Install a module (scaffold — see docs/operations/REGISTRY.md)
|
|
248
|
+
url Show the active registry URL
|
|
249
|
+
set-url <url> Override the registry URL (persists to user config)
|
|
250
|
+
|
|
251
|
+
Env overrides:
|
|
252
|
+
DOTFILES_REGISTRY_URL One-shot override of the registry URL.
|
|
253
|
+
|
|
254
|
+
Default registry: $(_registry_default_url)
|
|
255
|
+
EOF
|
|
256
|
+
;;
|
|
257
|
+
*)
|
|
258
|
+
ui_err "Unknown subcommand" "$subcommand"
|
|
259
|
+
echo "Run 'dot registry --help' for usage." >&2
|
|
260
|
+
return 1
|
|
261
|
+
;;
|
|
262
|
+
esac
|
|
263
|
+
}
|
|
@@ -353,7 +353,55 @@ cmd_env_mise() {
|
|
|
353
353
|
exit 1
|
|
354
354
|
fi
|
|
355
355
|
case "${1:-list}" in
|
|
356
|
-
list | ls)
|
|
356
|
+
list | ls)
|
|
357
|
+
if has_command jq; then
|
|
358
|
+
ui_table_begin "Tool" "Version" "Source" "Requested"
|
|
359
|
+
while IFS=$'\t' read -r tool ver source req; do
|
|
360
|
+
ui_table_add "$tool" "$ver" "${source/$HOME/\~}" "$req"
|
|
361
|
+
done < <(mise ls --json 2>/dev/null | jq -r '
|
|
362
|
+
to_entries[] as $t
|
|
363
|
+
| $t.value[]
|
|
364
|
+
| [
|
|
365
|
+
$t.key,
|
|
366
|
+
(.version // "-"),
|
|
367
|
+
(.source.path // ""),
|
|
368
|
+
(.requested_version // "")
|
|
369
|
+
]
|
|
370
|
+
| @tsv
|
|
371
|
+
')
|
|
372
|
+
ui_table_end
|
|
373
|
+
else
|
|
374
|
+
mise ls
|
|
375
|
+
fi
|
|
376
|
+
# Surface orphan installs (tool versions on disk that no active config
|
|
377
|
+
# claims). `--dry-run-code` exits 1 iff there is something to prune;
|
|
378
|
+
# the call is fast (no I/O beyond reading the tracked-configs index).
|
|
379
|
+
if mise prune --dry-run-code --quiet >/dev/null 2>&1; then
|
|
380
|
+
:
|
|
381
|
+
else
|
|
382
|
+
echo ""
|
|
383
|
+
ui_warn "orphan installs" "tool versions on disk are not requested by any config"
|
|
384
|
+
ui_info "to clean" "run 'dot env prune' (dry-run) or 'dot env prune --yes' to commit"
|
|
385
|
+
fi
|
|
386
|
+
;;
|
|
387
|
+
prune)
|
|
388
|
+
shift
|
|
389
|
+
local commit=0
|
|
390
|
+
for arg in "$@"; do
|
|
391
|
+
case "$arg" in
|
|
392
|
+
--yes | -y) commit=1 ;;
|
|
393
|
+
esac
|
|
394
|
+
done
|
|
395
|
+
if ((commit)); then
|
|
396
|
+
mise prune
|
|
397
|
+
else
|
|
398
|
+
ui_info "dry-run" "showing what 'mise prune' would remove (pass --yes to commit)"
|
|
399
|
+
echo ""
|
|
400
|
+
mise prune --dry-run
|
|
401
|
+
echo ""
|
|
402
|
+
ui_info "next" "re-run with 'dot env prune --yes' to actually uninstall"
|
|
403
|
+
fi
|
|
404
|
+
;;
|
|
357
405
|
install)
|
|
358
406
|
shift
|
|
359
407
|
# Validate tool names to prevent injection
|
package/scripts/dot/lib/bento.sh
CHANGED
|
@@ -31,7 +31,7 @@ _dotfiles_bento_render() {
|
|
|
31
31
|
|
|
32
32
|
# Render a fixed-width card (42 cols) so output aligns in any terminal width
|
|
33
33
|
printf "\n"
|
|
34
|
-
printf " ${c_cyan}${c_bold}💎 D O T F I L E S${c_reset} ${c_slate}[v0.2.
|
|
34
|
+
printf " ${c_cyan}${c_bold}💎 D O T F I L E S${c_reset} ${c_slate}[v0.2.502]${c_reset}\n"
|
|
35
35
|
printf " ${c_slate}──────────────────────────────────────────${c_reset}\n"
|
|
36
36
|
|
|
37
37
|
# Key system properties at a glance — answers "is my env healthy?" in 1 second
|
|
@@ -39,12 +39,22 @@ dot_host_os() {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
# Convert host-native path into Linux path when inside WSL.
|
|
42
|
+
#
|
|
43
|
+
# Contract:
|
|
44
|
+
# - In WSL: requires `wslpath`. Returns 2 (with a stderr error) if
|
|
45
|
+
# missing — callers must not silently treat a Windows-style path
|
|
46
|
+
# as Linux-safe.
|
|
47
|
+
# - Outside WSL: paths are already Linux/macOS paths; echoes the
|
|
48
|
+
# input unchanged and returns 0.
|
|
49
|
+
# - Empty input returns 1 (usage error).
|
|
42
50
|
dot_path_to_unix() {
|
|
43
51
|
local p="${1:-}"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
[[ -n "$p" ]] || return 1
|
|
53
|
+
if dot_is_wsl; then
|
|
54
|
+
if ! command -v wslpath >/dev/null 2>&1; then
|
|
55
|
+
printf "dot_path_to_unix: wslpath required in WSL but not found\n" >&2
|
|
56
|
+
return 2
|
|
57
|
+
fi
|
|
48
58
|
wslpath -u "$p"
|
|
49
59
|
return
|
|
50
60
|
fi
|
|
@@ -52,12 +62,15 @@ dot_path_to_unix() {
|
|
|
52
62
|
}
|
|
53
63
|
|
|
54
64
|
# Convert Linux path into host-native path when inside WSL.
|
|
65
|
+
# Same contract as dot_path_to_unix (see above).
|
|
55
66
|
dot_path_to_native() {
|
|
56
67
|
local p="${1:-}"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
68
|
+
[[ -n "$p" ]] || return 1
|
|
69
|
+
if dot_is_wsl; then
|
|
70
|
+
if ! command -v wslpath >/dev/null 2>&1; then
|
|
71
|
+
printf "dot_path_to_native: wslpath required in WSL but not found\n" >&2
|
|
72
|
+
return 2
|
|
73
|
+
fi
|
|
61
74
|
wslpath -w "$p"
|
|
62
75
|
return
|
|
63
76
|
fi
|