dotsync 0.1.24 → 0.1.26
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/.github/workflows/{gem-push.yml → ci.yml} +1 -1
- data/.github/workflows/release.yml +50 -0
- data/.gitignore +2 -0
- data/.pre-commit-config.yaml +9 -0
- data/CHANGELOG.md +55 -0
- data/Gemfile.lock +1 -1
- data/README.md +290 -74
- data/Rakefile +123 -9
- data/exe/dotsync +43 -3
- data/lib/dotsync/config/concerns/sync_mappings.rb +191 -0
- data/lib/dotsync/config/pull_action_config.rb +23 -5
- data/lib/dotsync/config/push_action_config.rb +24 -5
- data/lib/dotsync/loaders/pull_loader.rb +4 -0
- data/lib/dotsync/loaders/push_loader.rb +3 -0
- data/lib/dotsync/runner.rb +1 -0
- data/lib/dotsync/utils/directory_differ.rb +1 -1
- data/lib/dotsync/version.rb +1 -1
- data/lib/dotsync.rb +1 -0
- metadata +7 -8
- data/.github/workflows/gem-push.yml.bak +0 -45
data/Rakefile
CHANGED
|
@@ -15,23 +15,137 @@ end
|
|
|
15
15
|
task default: :spec
|
|
16
16
|
|
|
17
17
|
namespace :release do
|
|
18
|
-
desc "
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
version =
|
|
18
|
+
desc "Generate CHANGELOG entry for a new version"
|
|
19
|
+
# Usage: rake release:changelog[0.2.1]
|
|
20
|
+
task :changelog, [:version] do |_t, args|
|
|
21
|
+
version = args[:version]
|
|
22
|
+
unless version
|
|
23
|
+
require_relative "./lib/dotsync/version"
|
|
24
|
+
version = Dotsync::VERSION
|
|
25
|
+
end
|
|
26
|
+
version = version.sub(/^v/, "")
|
|
27
|
+
today = Date.today.strftime("%Y-%m-%d")
|
|
28
|
+
|
|
29
|
+
latest_tag = `git describe --tags --abbrev=0 2>/dev/null`.strip
|
|
30
|
+
commits = if latest_tag.empty?
|
|
31
|
+
`git log --oneline --no-decorate`.strip.split("\n")
|
|
32
|
+
else
|
|
33
|
+
`git log #{latest_tag}..HEAD --oneline --no-decorate`.strip.split("\n")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
if commits.empty?
|
|
37
|
+
abort "No commits since #{latest_tag}. Nothing to release."
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
categories = { "Added" => [], "Changed" => [], "Fixed" => [], "Removed" => [], "Security" => [], "Dependencies" => [] }
|
|
41
|
+
|
|
42
|
+
commits.each do |commit|
|
|
43
|
+
message = commit.sub(/^[a-f0-9]+\s+/, "")
|
|
44
|
+
case message.downcase
|
|
45
|
+
when /^add|^feat|^implement|^create|^new|^support/i then categories["Added"] << message
|
|
46
|
+
when /^fix|^bugfix|^hotfix|^resolve|^correct/i then categories["Fixed"] << message
|
|
47
|
+
when /^remove|^delete|^drop/i then categories["Removed"] << message
|
|
48
|
+
when /^security|^vuln|^cve/i then categories["Security"] << message
|
|
49
|
+
when /^bump|^upgrade|^update.*dependency|^dep/i then categories["Dependencies"] << message
|
|
50
|
+
else categories["Changed"] << message
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
changelog_entry = "## [#{version}] - #{today}\n"
|
|
55
|
+
categories.each do |category, items|
|
|
56
|
+
next if items.empty?
|
|
57
|
+
changelog_entry += "\n### #{category}\n\n"
|
|
58
|
+
items.each { |item| changelog_entry += "- #{item}\n" }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
changelog_path = "CHANGELOG.md"
|
|
62
|
+
abort "CHANGELOG.md not found." unless File.exist?(changelog_path)
|
|
63
|
+
|
|
64
|
+
changelog = File.read(changelog_path)
|
|
65
|
+
abort "Version #{version} already exists in CHANGELOG.md" if changelog.include?("[#{version}]")
|
|
66
|
+
|
|
67
|
+
match = changelog.match(/^## \[/m)
|
|
68
|
+
new_changelog = match ? changelog.sub(/^## \[/m, "#{changelog_entry}\n## [") : changelog.rstrip + "\n\n#{changelog_entry}"
|
|
69
|
+
|
|
70
|
+
repo_url = `git remote get-url origin`.strip.sub(/\.git$/, "").sub(/^git@github\.com:/, "https://github.com/")
|
|
71
|
+
previous_version = latest_tag.empty? ? "v0.0.0" : latest_tag
|
|
72
|
+
link_entry = "[#{version}]: #{repo_url}/compare/#{previous_version}...v#{version}"
|
|
73
|
+
|
|
74
|
+
new_changelog = if new_changelog.match?(/^\[[\d.]+\]:.*compare/m)
|
|
75
|
+
new_changelog.sub(/^(\[[\d.]+\]:.*compare)/m, "#{link_entry}\n\\1")
|
|
76
|
+
else
|
|
77
|
+
new_changelog.rstrip + "\n\n#{link_entry}\n"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
File.write(changelog_path, new_changelog)
|
|
81
|
+
puts "Updated CHANGELOG.md with version #{version}"
|
|
82
|
+
puts "\n#{changelog_entry}"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
desc "Create annotated git tag from CHANGELOG"
|
|
86
|
+
# Usage: rake release:tag[0.2.1] or just rake release:tag (uses Dotsync::VERSION)
|
|
87
|
+
task :tag, [:version] do |_t, args|
|
|
88
|
+
version = args[:version]
|
|
89
|
+
unless version
|
|
90
|
+
require_relative "./lib/dotsync/version"
|
|
91
|
+
version = Dotsync::VERSION
|
|
92
|
+
end
|
|
93
|
+
version = version.sub(/^v/, "")
|
|
22
94
|
tag_name = "v#{version}"
|
|
23
95
|
|
|
24
|
-
# Check if tag already exists
|
|
25
96
|
if `git tag --list`.split.include?(tag_name)
|
|
26
97
|
puts "Tag #{tag_name} already exists."
|
|
27
98
|
exit(1)
|
|
28
99
|
end
|
|
29
100
|
|
|
101
|
+
changelog_path = "CHANGELOG.md"
|
|
102
|
+
abort "CHANGELOG.md not found." unless File.exist?(changelog_path)
|
|
103
|
+
|
|
104
|
+
changelog = File.read(changelog_path)
|
|
105
|
+
version_regex = /^## \[#{Regexp.escape(version)}\] - (\d{4}-\d{2}-\d{2})\n(.*?)(?=^## \[|\z)/m
|
|
106
|
+
match = changelog.match(version_regex)
|
|
107
|
+
|
|
108
|
+
unless match
|
|
109
|
+
abort "Version #{version} not found in CHANGELOG.md\nRun 'rake release:changelog[#{version}]' first."
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
date = match[1]
|
|
113
|
+
content = match[2].strip
|
|
114
|
+
tag_message = "#{version} - #{date}\n\n#{content}"
|
|
115
|
+
|
|
30
116
|
puts "Tagging commit as #{tag_name}..."
|
|
31
|
-
sh "git tag -a
|
|
32
|
-
puts "
|
|
33
|
-
|
|
34
|
-
|
|
117
|
+
sh "git", "tag", "-a", tag_name, "-m", tag_message
|
|
118
|
+
puts "Tag created. Push with: git push origin #{tag_name}"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
desc "Full release: update changelog, commit, tag, and push"
|
|
122
|
+
# Usage: rake release:publish[0.2.1] or rake release:publish (uses Dotsync::VERSION)
|
|
123
|
+
task :publish, [:version] do |_t, args|
|
|
124
|
+
version = args[:version]
|
|
125
|
+
unless version
|
|
126
|
+
require_relative "./lib/dotsync/version"
|
|
127
|
+
version = Dotsync::VERSION
|
|
128
|
+
end
|
|
129
|
+
version = version.sub(/^v/, "")
|
|
130
|
+
|
|
131
|
+
status = `git status --porcelain`.strip
|
|
132
|
+
uncommitted = status.split("\n").reject { |line| line.end_with?("CHANGELOG.md") }
|
|
133
|
+
abort "Uncommitted changes:\n#{uncommitted.join("\n")}" unless uncommitted.empty?
|
|
134
|
+
|
|
135
|
+
Rake::Task["release:changelog"].invoke(version)
|
|
136
|
+
puts "\nReview CHANGELOG.md, then press Enter to continue..."
|
|
137
|
+
$stdin.gets
|
|
138
|
+
|
|
139
|
+
sh "git", "add", "CHANGELOG.md"
|
|
140
|
+
sh "git", "commit", "-m", "Update CHANGELOG for v#{version}\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>"
|
|
141
|
+
|
|
142
|
+
Rake::Task["release:tag"].reenable
|
|
143
|
+
Rake::Task["release:tag"].invoke(version)
|
|
144
|
+
|
|
145
|
+
branch = `git rev-parse --abbrev-ref HEAD`.strip
|
|
146
|
+
sh "git", "push", "origin", branch
|
|
147
|
+
sh "git", "push", "origin", "v#{version}"
|
|
148
|
+
puts "\n✅ Released v#{version}"
|
|
35
149
|
end
|
|
36
150
|
end
|
|
37
151
|
|
data/exe/dotsync
CHANGED
|
@@ -6,6 +6,10 @@ require_relative "../lib/dotsync/version"
|
|
|
6
6
|
|
|
7
7
|
options = { apply: false, config_path: nil }
|
|
8
8
|
|
|
9
|
+
# Initialize logger early for consistent error handling
|
|
10
|
+
require_relative "../lib/dotsync/core"
|
|
11
|
+
logger = Dotsync::Logger.new
|
|
12
|
+
|
|
9
13
|
opt_parser = OptionParser.new do |opts|
|
|
10
14
|
opts.banner = <<~BANNER
|
|
11
15
|
dotsync #{Dotsync::VERSION}
|
|
@@ -30,6 +34,7 @@ opt_parser = OptionParser.new do |opts|
|
|
|
30
34
|
dotsync diff # Show what would change
|
|
31
35
|
dotsync watch # Monitor and sync continuously
|
|
32
36
|
dotsync -c ~/custom.toml push # Use custom config file
|
|
37
|
+
dotsync push --trace # Show full error backtraces
|
|
33
38
|
|
|
34
39
|
Options:
|
|
35
40
|
-a, --apply Apply changes (push or pull)
|
|
@@ -46,6 +51,7 @@ opt_parser = OptionParser.new do |opts|
|
|
|
46
51
|
--only-config Show only the config section
|
|
47
52
|
--only-mappings Show only the mappings section
|
|
48
53
|
-v, --verbose Force showing all available information
|
|
54
|
+
--trace Show full error backtraces (for debugging)
|
|
49
55
|
--version Show version number
|
|
50
56
|
-h, --help Show this help message
|
|
51
57
|
BANNER
|
|
@@ -106,6 +112,10 @@ opt_parser = OptionParser.new do |opts|
|
|
|
106
112
|
options[:verbose] = true
|
|
107
113
|
end
|
|
108
114
|
|
|
115
|
+
opts.on("--trace", "Show full error backtraces (for debugging)") do
|
|
116
|
+
options[:trace] = true
|
|
117
|
+
end
|
|
118
|
+
|
|
109
119
|
opts.on("--version", "Show version number") do
|
|
110
120
|
puts "dotsync #{Dotsync::VERSION}"
|
|
111
121
|
exit
|
|
@@ -117,12 +127,29 @@ opt_parser = OptionParser.new do |opts|
|
|
|
117
127
|
end
|
|
118
128
|
end
|
|
119
129
|
|
|
120
|
-
|
|
130
|
+
begin
|
|
131
|
+
opt_parser.parse!
|
|
132
|
+
rescue OptionParser::InvalidOption, OptionParser::InvalidArgument => e
|
|
133
|
+
# Extract just the problematic option from the error message
|
|
134
|
+
option_match = e.message.match(/invalid (?:option|argument): (.+)/)
|
|
135
|
+
problem = option_match ? option_match[1] : e.message
|
|
136
|
+
|
|
137
|
+
logger.error("Invalid option: #{problem}")
|
|
138
|
+
logger.info("See 'dotsync --help' for available options")
|
|
139
|
+
|
|
140
|
+
if options[:trace]
|
|
141
|
+
logger.log("\nFull backtrace:", color: 240)
|
|
142
|
+
e.backtrace.each { |line| logger.log(" #{line}", color: 240) }
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
exit 1
|
|
146
|
+
end
|
|
121
147
|
|
|
122
148
|
command = ARGV.shift
|
|
123
149
|
|
|
124
150
|
# Load only the dependencies needed for the specific command
|
|
125
|
-
|
|
151
|
+
begin
|
|
152
|
+
case command
|
|
126
153
|
when "push"
|
|
127
154
|
require_relative "../lib/dotsync/loaders/push_loader"
|
|
128
155
|
Dotsync::Runner.new(config_path: options[:config_path]).run(:push, options)
|
|
@@ -153,9 +180,22 @@ when "diff"
|
|
|
153
180
|
Dotsync::Runner.new(config_path: options[:config_path]).run(:push, diff_options)
|
|
154
181
|
else
|
|
155
182
|
if command
|
|
156
|
-
|
|
183
|
+
logger.error("Unknown command: '#{command}'")
|
|
184
|
+
logger.info("See 'dotsync --help' for available commands")
|
|
157
185
|
else
|
|
158
186
|
puts opt_parser.banner
|
|
159
187
|
end
|
|
160
188
|
exit 1
|
|
189
|
+
end
|
|
190
|
+
rescue => e
|
|
191
|
+
logger.error("Unexpected error: #{e.message}")
|
|
192
|
+
|
|
193
|
+
if options[:trace]
|
|
194
|
+
logger.log("\nFull backtrace:", color: 240)
|
|
195
|
+
e.backtrace.each { |line| logger.log(" #{line}", color: 240) }
|
|
196
|
+
else
|
|
197
|
+
logger.info("Run with --trace to see full error details")
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
exit 1
|
|
161
201
|
end
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dotsync
|
|
4
|
+
# SyncMappings provides bidirectional mapping support.
|
|
5
|
+
# It reads [sync] section mappings and converts them to push or pull format.
|
|
6
|
+
#
|
|
7
|
+
# The [sync] section supports multiple sub-types:
|
|
8
|
+
#
|
|
9
|
+
# 1. Explicit mappings with [[sync.mappings]]:
|
|
10
|
+
# [[sync.mappings]]
|
|
11
|
+
# local = "$XDG_CONFIG_HOME/nvim"
|
|
12
|
+
# remote = "$XDG_CONFIG_HOME_MIRROR/nvim"
|
|
13
|
+
# force = true
|
|
14
|
+
# ignore = ["lazy-lock.json"]
|
|
15
|
+
#
|
|
16
|
+
# 2. XDG shorthand mappings that auto-expand environment variables:
|
|
17
|
+
# [[sync.xdg_config]]
|
|
18
|
+
# path = "nvim"
|
|
19
|
+
# force = true
|
|
20
|
+
# # Expands to: local=$XDG_CONFIG_HOME/nvim, remote=$XDG_CONFIG_HOME_MIRROR/nvim
|
|
21
|
+
#
|
|
22
|
+
# Supported shorthands:
|
|
23
|
+
# - sync.home: $HOME <-> $HOME_MIRROR
|
|
24
|
+
# - sync.xdg_config: $XDG_CONFIG_HOME <-> $XDG_CONFIG_HOME_MIRROR
|
|
25
|
+
# - sync.xdg_data: $XDG_DATA_HOME <-> $XDG_DATA_HOME_MIRROR
|
|
26
|
+
# - sync.xdg_cache: $XDG_CACHE_HOME <-> $XDG_CACHE_HOME_MIRROR
|
|
27
|
+
# - sync.xdg_bin: $XDG_BIN_HOME <-> $XDG_BIN_HOME_MIRROR
|
|
28
|
+
# - sync.mappings: explicit local/remote mappings
|
|
29
|
+
#
|
|
30
|
+
# For push: local → remote (src=local, dest=remote)
|
|
31
|
+
# For pull: remote → local (src=remote, dest=local)
|
|
32
|
+
module SyncMappings
|
|
33
|
+
SYNC_SECTION = "sync"
|
|
34
|
+
MAPPINGS_KEY = "mappings"
|
|
35
|
+
|
|
36
|
+
# Shorthand type definitions mapping to local/remote base paths
|
|
37
|
+
SHORTHANDS = {
|
|
38
|
+
"home" => { local: "$HOME", remote: "$HOME_MIRROR" },
|
|
39
|
+
"xdg_config" => { local: "$XDG_CONFIG_HOME", remote: "$XDG_CONFIG_HOME_MIRROR" },
|
|
40
|
+
"xdg_data" => { local: "$XDG_DATA_HOME", remote: "$XDG_DATA_HOME_MIRROR" },
|
|
41
|
+
"xdg_cache" => { local: "$XDG_CACHE_HOME", remote: "$XDG_CACHE_HOME_MIRROR" },
|
|
42
|
+
"xdg_bin" => { local: "$XDG_BIN_HOME", remote: "$XDG_BIN_HOME_MIRROR" }
|
|
43
|
+
}.freeze
|
|
44
|
+
|
|
45
|
+
def sync_mappings_for_push
|
|
46
|
+
all_sync_mappings(:push)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def sync_mappings_for_pull
|
|
50
|
+
all_sync_mappings(:pull)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def has_sync_mappings?
|
|
54
|
+
return false unless sync_section.is_a?(Hash)
|
|
55
|
+
|
|
56
|
+
explicit_mappings_raw.any? || shorthand_mappings_raw.any?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
def all_sync_mappings(direction)
|
|
61
|
+
convert_explicit_mappings(direction: direction) + convert_shorthand_mappings(direction: direction)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def sync_section
|
|
65
|
+
@config[SYNC_SECTION]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Explicit [[sync.mappings]] with local/remote keys
|
|
69
|
+
def explicit_mappings_raw
|
|
70
|
+
return [] unless sync_section.is_a?(Hash)
|
|
71
|
+
return [] unless sync_section.key?(MAPPINGS_KEY)
|
|
72
|
+
|
|
73
|
+
Array(sync_section[MAPPINGS_KEY])
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def convert_explicit_mappings(direction:)
|
|
77
|
+
explicit_mappings_raw.map do |mapping|
|
|
78
|
+
converted = convert_explicit_mapping(mapping, direction)
|
|
79
|
+
Dotsync::Mapping.new(converted)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def convert_explicit_mapping(mapping, direction)
|
|
84
|
+
local = mapping["local"]
|
|
85
|
+
remote = mapping["remote"]
|
|
86
|
+
|
|
87
|
+
base = case direction
|
|
88
|
+
when :push
|
|
89
|
+
{ "src" => local, "dest" => remote }
|
|
90
|
+
when :pull
|
|
91
|
+
{ "src" => remote, "dest" => local }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Preserve other options
|
|
95
|
+
base["force"] = mapping["force"] if mapping.key?("force")
|
|
96
|
+
base["ignore"] = mapping["ignore"] if mapping.key?("ignore")
|
|
97
|
+
base["only"] = mapping["only"] if mapping.key?("only")
|
|
98
|
+
|
|
99
|
+
base
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Shorthand mappings: [[sync.home]], [[sync.xdg_config]], etc.
|
|
103
|
+
def shorthand_mappings_raw
|
|
104
|
+
return [] unless sync_section.is_a?(Hash)
|
|
105
|
+
|
|
106
|
+
mappings = []
|
|
107
|
+
SHORTHANDS.each_key do |shorthand_type|
|
|
108
|
+
next unless sync_section.key?(shorthand_type)
|
|
109
|
+
Array(sync_section[shorthand_type]).each do |mapping|
|
|
110
|
+
mappings << { type: shorthand_type, mapping: mapping }
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
mappings
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def convert_shorthand_mappings(direction:)
|
|
117
|
+
shorthand_mappings_raw.map do |entry|
|
|
118
|
+
converted = convert_shorthand_mapping(entry[:type], entry[:mapping], direction)
|
|
119
|
+
Dotsync::Mapping.new(converted)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def convert_shorthand_mapping(shorthand_type, mapping, direction)
|
|
124
|
+
shorthand_def = SHORTHANDS[shorthand_type]
|
|
125
|
+
|
|
126
|
+
# Support both 'path' for single paths and 'only' for multiple paths
|
|
127
|
+
path = mapping["path"]
|
|
128
|
+
only = mapping["only"]
|
|
129
|
+
|
|
130
|
+
local = build_path(shorthand_def[:local], path)
|
|
131
|
+
remote = build_path(shorthand_def[:remote], path)
|
|
132
|
+
|
|
133
|
+
base = case direction
|
|
134
|
+
when :push
|
|
135
|
+
{ "src" => local, "dest" => remote }
|
|
136
|
+
when :pull
|
|
137
|
+
{ "src" => remote, "dest" => local }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Preserve other options
|
|
141
|
+
base["force"] = mapping["force"] if mapping.key?("force")
|
|
142
|
+
base["ignore"] = mapping["ignore"] if mapping.key?("ignore")
|
|
143
|
+
base["only"] = only if only
|
|
144
|
+
|
|
145
|
+
base
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def build_path(base, path)
|
|
149
|
+
path ? File.join(base, path) : base
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def validate_sync_mappings!
|
|
153
|
+
return unless sync_section
|
|
154
|
+
|
|
155
|
+
unless sync_section.is_a?(Hash)
|
|
156
|
+
raise Dotsync::ConfigError,
|
|
157
|
+
"Configuration error: [sync] must be a table, not an array. " \
|
|
158
|
+
"Use [[sync.mappings]] for explicit mappings."
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
validate_explicit_mappings!
|
|
162
|
+
validate_shorthand_mappings!
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def validate_explicit_mappings!
|
|
166
|
+
return unless sync_section.key?(MAPPINGS_KEY)
|
|
167
|
+
|
|
168
|
+
explicit_mappings_raw.each_with_index do |mapping, index|
|
|
169
|
+
unless mapping.is_a?(Hash) && mapping.key?("local") && mapping.key?("remote")
|
|
170
|
+
raise Dotsync::ConfigError,
|
|
171
|
+
"Configuration error in sync.mappings ##{index + 1}: " \
|
|
172
|
+
"Each mapping must have 'local' and 'remote' keys."
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def validate_shorthand_mappings!
|
|
178
|
+
SHORTHANDS.each_key do |shorthand_type|
|
|
179
|
+
next unless sync_section.key?(shorthand_type)
|
|
180
|
+
|
|
181
|
+
Array(sync_section[shorthand_type]).each_with_index do |mapping, index|
|
|
182
|
+
unless mapping.is_a?(Hash)
|
|
183
|
+
raise Dotsync::ConfigError,
|
|
184
|
+
"Configuration error in sync.#{shorthand_type} ##{index + 1}: " \
|
|
185
|
+
"Each mapping must be a table."
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
module Dotsync
|
|
4
4
|
class PullActionConfig < BaseConfig
|
|
5
5
|
include XDGBaseDirectory
|
|
6
|
+
include SyncMappings
|
|
6
7
|
|
|
7
8
|
def mappings
|
|
8
|
-
|
|
9
|
-
Array(mappings_list).map { |mapping| Dotsync::Mapping.new(mapping) }
|
|
9
|
+
section_mappings + sync_mappings_for_pull
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def backups_root
|
|
@@ -20,13 +20,31 @@ module Dotsync
|
|
|
20
20
|
SECTION_NAME
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
def section_mappings
|
|
24
|
+
return [] unless section && section["mappings"]
|
|
25
|
+
Array(section["mappings"]).map { |mapping| Dotsync::Mapping.new(mapping) }
|
|
26
|
+
end
|
|
27
|
+
|
|
23
28
|
def validate!
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
validate_pull_or_sync_present!
|
|
30
|
+
validate_pull_mappings!
|
|
31
|
+
validate_sync_mappings!
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def validate_pull_or_sync_present!
|
|
35
|
+
has_pull = @config.key?(section_name) && section["mappings"]&.any?
|
|
36
|
+
|
|
37
|
+
unless has_pull || has_sync_mappings?
|
|
38
|
+
raise_error "No [#{section_name}] mappings or [sync] mappings found in config file"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def validate_pull_mappings!
|
|
43
|
+
return unless section && section["mappings"]
|
|
26
44
|
|
|
27
45
|
Array(section["mappings"]).each_with_index do |mapping, index|
|
|
28
46
|
unless mapping.is_a?(Hash) && mapping.key?("src") && mapping.key?("dest")
|
|
29
|
-
raise "Configuration error in mapping ##{index + 1}: Each mapping must have 'src' and 'dest' keys."
|
|
47
|
+
raise "Configuration error in pull mapping ##{index + 1}: Each mapping must have 'src' and 'dest' keys."
|
|
30
48
|
end
|
|
31
49
|
end
|
|
32
50
|
end
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Dotsync
|
|
4
4
|
class PushActionConfig < BaseConfig
|
|
5
|
+
include SyncMappings
|
|
6
|
+
|
|
5
7
|
def mappings
|
|
6
|
-
|
|
7
|
-
Array(mappings_list).map { |mapping| Dotsync::Mapping.new(mapping) }
|
|
8
|
+
section_mappings + sync_mappings_for_push
|
|
8
9
|
end
|
|
9
10
|
|
|
10
11
|
private
|
|
@@ -14,13 +15,31 @@ module Dotsync
|
|
|
14
15
|
SECTION_NAME
|
|
15
16
|
end
|
|
16
17
|
|
|
18
|
+
def section_mappings
|
|
19
|
+
return [] unless section && section["mappings"]
|
|
20
|
+
Array(section["mappings"]).map { |mapping| Dotsync::Mapping.new(mapping) }
|
|
21
|
+
end
|
|
22
|
+
|
|
17
23
|
def validate!
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
validate_push_or_sync_present!
|
|
25
|
+
validate_push_mappings!
|
|
26
|
+
validate_sync_mappings!
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def validate_push_or_sync_present!
|
|
30
|
+
has_push = @config.key?(section_name) && section["mappings"]&.any?
|
|
31
|
+
|
|
32
|
+
unless has_push || has_sync_mappings?
|
|
33
|
+
raise_error "No [#{section_name}] mappings or [sync] mappings found in config file"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def validate_push_mappings!
|
|
38
|
+
return unless section && section["mappings"]
|
|
20
39
|
|
|
21
40
|
Array(section["mappings"]).each_with_index do |mapping, index|
|
|
22
41
|
unless mapping.is_a?(Hash) && mapping.key?("src") && mapping.key?("dest")
|
|
23
|
-
raise "Configuration error in mapping ##{index + 1}: Each mapping must have 'src' and 'dest' keys."
|
|
42
|
+
raise "Configuration error in push mapping ##{index + 1}: Each mapping must have 'src' and 'dest' keys."
|
|
24
43
|
end
|
|
25
44
|
end
|
|
26
45
|
end
|
|
@@ -16,6 +16,10 @@ require_relative "../utils/config_cache"
|
|
|
16
16
|
require_relative "../models/mapping"
|
|
17
17
|
require_relative "../models/diff"
|
|
18
18
|
|
|
19
|
+
# Config Concerns
|
|
20
|
+
require_relative "../config/concerns/xdg_base_directory"
|
|
21
|
+
require_relative "../config/concerns/sync_mappings"
|
|
22
|
+
|
|
19
23
|
# Config
|
|
20
24
|
require_relative "../config/base_config"
|
|
21
25
|
require_relative "../config/pull_action_config"
|
|
@@ -16,6 +16,9 @@ require_relative "../utils/config_cache"
|
|
|
16
16
|
require_relative "../models/mapping"
|
|
17
17
|
require_relative "../models/diff"
|
|
18
18
|
|
|
19
|
+
# Config Concerns
|
|
20
|
+
require_relative "../config/concerns/sync_mappings"
|
|
21
|
+
|
|
19
22
|
# Config
|
|
20
23
|
require_relative "../config/base_config"
|
|
21
24
|
require_relative "../config/push_action_config"
|
data/lib/dotsync/runner.rb
CHANGED
data/lib/dotsync/version.rb
CHANGED
data/lib/dotsync.rb
CHANGED
|
@@ -22,6 +22,7 @@ require_relative "dotsync/version"
|
|
|
22
22
|
|
|
23
23
|
# Config Concerns (loaded early as they're used by other modules)
|
|
24
24
|
require_relative "dotsync/config/concerns/xdg_base_directory"
|
|
25
|
+
require_relative "dotsync/config/concerns/sync_mappings"
|
|
25
26
|
|
|
26
27
|
# Utils
|
|
27
28
|
require_relative "dotsync/utils/path_utils"
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dotsync
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.26
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Sáenz
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: toml-rb
|
|
@@ -285,9 +284,10 @@ extensions: []
|
|
|
285
284
|
extra_rdoc_files: []
|
|
286
285
|
files:
|
|
287
286
|
- ".editorconfig"
|
|
288
|
-
- ".github/workflows/
|
|
289
|
-
- ".github/workflows/
|
|
287
|
+
- ".github/workflows/ci.yml"
|
|
288
|
+
- ".github/workflows/release.yml"
|
|
290
289
|
- ".gitignore"
|
|
290
|
+
- ".pre-commit-config.yaml"
|
|
291
291
|
- ".rspec"
|
|
292
292
|
- ".rubocop.yml"
|
|
293
293
|
- ".ruby-version"
|
|
@@ -317,6 +317,7 @@ files:
|
|
|
317
317
|
- lib/dotsync/actions/watch_action.rb
|
|
318
318
|
- lib/dotsync/colors.rb
|
|
319
319
|
- lib/dotsync/config/base_config.rb
|
|
320
|
+
- lib/dotsync/config/concerns/sync_mappings.rb
|
|
320
321
|
- lib/dotsync/config/concerns/xdg_base_directory.rb
|
|
321
322
|
- lib/dotsync/config/pull_action_config.rb
|
|
322
323
|
- lib/dotsync/config/push_action_config.rb
|
|
@@ -346,7 +347,6 @@ metadata:
|
|
|
346
347
|
homepage_uri: https://github.com/dsaenztagarro/dotsync
|
|
347
348
|
source_code_uri: https://github.com/dsaenztagarro/dotsync
|
|
348
349
|
changelog_uri: https://github.com/dsaenztagarro/dotsync/blob/master/CHANGELOG.md
|
|
349
|
-
post_install_message:
|
|
350
350
|
rdoc_options: []
|
|
351
351
|
require_paths:
|
|
352
352
|
- lib
|
|
@@ -361,8 +361,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
361
361
|
- !ruby/object:Gem::Version
|
|
362
362
|
version: '0'
|
|
363
363
|
requirements: []
|
|
364
|
-
rubygems_version: 3.
|
|
365
|
-
signing_key:
|
|
364
|
+
rubygems_version: 3.7.2
|
|
366
365
|
specification_version: 4
|
|
367
366
|
summary: Manage dotfiles like a boss
|
|
368
367
|
test_files: []
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
jobs:
|
|
2
|
-
build:
|
|
3
|
-
name: Build + Publish
|
|
4
|
-
runs-on: ubuntu-latest
|
|
5
|
-
permissions:
|
|
6
|
-
contents: read
|
|
7
|
-
packages: write
|
|
8
|
-
|
|
9
|
-
steps:
|
|
10
|
-
- uses: actions/checkout@v4
|
|
11
|
-
|
|
12
|
-
- name: Publish to GPR
|
|
13
|
-
run: |
|
|
14
|
-
mkdir -p $HOME/.gem
|
|
15
|
-
touch $HOME/.gem/credentials
|
|
16
|
-
chmod 0600 $HOME/.gem/credentials
|
|
17
|
-
printf -- "---\n:github: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
|
|
18
|
-
gem build *.gemspec
|
|
19
|
-
gem push --KEY github --host https://rubygems.pkg.github.com/${OWNER} *.gem
|
|
20
|
-
env:
|
|
21
|
-
GEM_HOST_API_KEY: "Bearer ${{secrets.GITHUB_TOKEN}}"
|
|
22
|
-
OWNER: ${{ github.repository_owner }}
|
|
23
|
-
|
|
24
|
-
publish:
|
|
25
|
-
needs: test
|
|
26
|
-
runs-on: ubuntu-latest
|
|
27
|
-
if: github.ref == 'refs/heads/master' && matrix.ruby == '3.2'
|
|
28
|
-
|
|
29
|
-
steps:
|
|
30
|
-
- uses: actions/checkout@v4
|
|
31
|
-
|
|
32
|
-
- name: Set up Ruby
|
|
33
|
-
uses: ruby/setup-ruby@v1
|
|
34
|
-
with:
|
|
35
|
-
ruby-version: 3.2
|
|
36
|
-
bundler-cache: true
|
|
37
|
-
|
|
38
|
-
- name: Check if gem version has changed
|
|
39
|
-
id: gem_version_check
|
|
40
|
-
run: |
|
|
41
|
-
gem build *.gemspec
|
|
42
|
-
gem_name=$(ls *.gem | head -n 1)
|
|
43
|
-
gem info $gem_name --remote || echo "new_version" > version_changed
|
|
44
|
-
env:
|
|
45
|
-
GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
|