ro 4.4.0 → 5.1.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.
Files changed (161) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +42 -16
  3. data/MIGRATION.md +320 -0
  4. data/README.md +31 -19
  5. data/a.yml +60 -0
  6. data/bin/ro +10 -0
  7. data/lib/ro/_lib.rb +1 -1
  8. data/lib/ro/asset.rb +48 -6
  9. data/lib/ro/collection.rb +51 -13
  10. data/lib/ro/migrator.rb +285 -0
  11. data/lib/ro/node.rb +53 -13
  12. data/lib/ro/root.rb +75 -1
  13. data/lib/ro/script/migrate.rb +204 -0
  14. data/lib/ro/script/server.rb +1 -1
  15. data/lib/ro.rb +1 -0
  16. data/public/api/ro/index-1.json +82 -148
  17. data/public/api/ro/index.json +82 -148
  18. data/public/api/ro/nerd/fastest-possible-embeddings/index.json +7 -8
  19. data/public/api/ro/nerd/ima/index.json +3 -4
  20. data/public/api/ro/nerd/index/index.json +5 -6
  21. data/public/api/ro/nerd/index-1.json +15 -18
  22. data/public/api/ro/nerd/index.json +15 -18
  23. data/public/api/ro/pages/contact/index.json +4 -5
  24. data/public/api/ro/pages/cv/index.json +3 -4
  25. data/public/api/ro/pages/disco/index.json +9 -10
  26. data/public/api/ro/pages/index/index.json +2 -3
  27. data/public/api/ro/pages/index-1.json +25 -82
  28. data/public/api/ro/pages/index.json +25 -82
  29. data/public/api/ro/pages/jess/index.json +4 -5
  30. data/public/api/ro/pages/now/index.json +3 -4
  31. data/public/api/ro/posts/almost-died-in-an-ice-cave/index.json +21 -22
  32. data/public/api/ro/posts/facebook-and-global-extremism/index.json +8 -9
  33. data/public/api/ro/posts/index-1.json +42 -48
  34. data/public/api/ro/posts/index.json +42 -48
  35. data/public/api/ro/posts/lemmings-considered-harmful/index.json +3 -4
  36. data/public/api/ro/posts/lost-in-the-desert/index.json +3 -4
  37. data/public/api/ro/posts/mission/index.json +3 -4
  38. data/public/api/ro/posts/return-your-laptop/index.json +4 -5
  39. data/ro.gemspec +247 -18
  40. data/specs/001-simplify-asset-structure/IMPLEMENTATION_SUMMARY.md +212 -0
  41. data/specs/001-simplify-asset-structure/checklists/requirements.md +36 -0
  42. data/specs/001-simplify-asset-structure/contracts/collection_api.md +407 -0
  43. data/specs/001-simplify-asset-structure/contracts/migrator_api.md +461 -0
  44. data/specs/001-simplify-asset-structure/contracts/node_api.md +294 -0
  45. data/specs/001-simplify-asset-structure/data-model.md +381 -0
  46. data/specs/001-simplify-asset-structure/plan.md +90 -0
  47. data/specs/001-simplify-asset-structure/quickstart.md +575 -0
  48. data/specs/001-simplify-asset-structure/research.md +333 -0
  49. data/specs/001-simplify-asset-structure/spec.md +127 -0
  50. data/specs/001-simplify-asset-structure/tasks.md +349 -0
  51. data/test/fixtures/new_structure/mixed/test-json.json +5 -0
  52. data/test/fixtures/new_structure/mixed/test-yaml.yml +3 -0
  53. data/test/fixtures/new_structure/posts/metadata-only.yml +7 -0
  54. data/test/fixtures/new_structure/posts/nested-test/assets/subdirectory/image.png +2 -0
  55. data/test/fixtures/new_structure/posts/nested-test.yml +7 -0
  56. data/test/fixtures/new_structure/posts/sample-post/assets/body.md +5 -0
  57. data/test/fixtures/new_structure/posts/sample-post/assets/image.jpg +2 -0
  58. data/test/fixtures/new_structure/posts/sample-post.yml +7 -0
  59. data/test/fixtures/old_structure/posts/assets-only/assets/test.txt +1 -0
  60. data/test/fixtures/old_structure/posts/sample-post/assets/body.md +5 -0
  61. data/test/fixtures/old_structure/posts/sample-post/assets/image.jpg +2 -0
  62. data/test/fixtures/old_structure/posts/sample-post/attributes.yml +2 -0
  63. data/test/integration/ro_integration_test.rb +165 -0
  64. data/test/test_helper.rb +149 -0
  65. data/test/tmp/migration_test_1760746513.backup.20251018001513/migration_test_1760746513/posts/sample-post/assets/image.jpg +2 -0
  66. data/test/tmp/migration_test_1760746513.backup.20251018001513/migration_test_1760746513/posts/sample-post/attributes.yml +7 -0
  67. data/test/tmp/migration_test_1760746513.backup.20251018001513/migration_test_1760746513/posts/sample-post/body.md +5 -0
  68. data/test/tmp/migration_test_1760746513.backup.20251018001513/posts/sample-post/assets/image.jpg +2 -0
  69. data/test/tmp/migration_test_1760746513.backup.20251018001513/posts/sample-post/attributes.yml +7 -0
  70. data/test/tmp/migration_test_1760746513.backup.20251018001513/posts/sample-post/body.md +5 -0
  71. data/test/tmp/migration_test_1760746556.backup.20251018001556/migration_test_1760746556/posts/sample-post/assets/image.jpg +2 -0
  72. data/test/tmp/migration_test_1760746556.backup.20251018001556/migration_test_1760746556/posts/sample-post/attributes.yml +7 -0
  73. data/test/tmp/migration_test_1760746556.backup.20251018001556/migration_test_1760746556/posts/sample-post/body.md +5 -0
  74. data/test/tmp/migration_test_1760746556.backup.20251018001556/posts/sample-post/assets/image.jpg +2 -0
  75. data/test/tmp/migration_test_1760746556.backup.20251018001556/posts/sample-post/attributes.yml +7 -0
  76. data/test/tmp/migration_test_1760746556.backup.20251018001556/posts/sample-post/body.md +5 -0
  77. data/test/tmp/migration_test_1760755248.backup.20251018024048/migration_test_1760755248/posts/sample-post/assets/image.jpg +2 -0
  78. data/test/tmp/migration_test_1760755248.backup.20251018024048/migration_test_1760755248/posts/sample-post/attributes.yml +7 -0
  79. data/test/tmp/migration_test_1760755248.backup.20251018024048/migration_test_1760755248/posts/sample-post/body.md +5 -0
  80. data/test/tmp/migration_test_1760755248.backup.20251018024048/posts/sample-post/assets/image.jpg +2 -0
  81. data/test/tmp/migration_test_1760755248.backup.20251018024048/posts/sample-post/attributes.yml +7 -0
  82. data/test/tmp/migration_test_1760755248.backup.20251018024048/posts/sample-post/body.md +5 -0
  83. data/test/tmp/migration_test_1760758803.backup.20251018034003/migration_test_1760758803/posts/sample-post/body.md +5 -0
  84. data/test/tmp/migration_test_1760758803.backup.20251018034003/migration_test_1760758803/posts/sample-post/image.jpg +2 -0
  85. data/test/tmp/migration_test_1760758803.backup.20251018034003/migration_test_1760758803/posts/sample-post.yml +7 -0
  86. data/test/tmp/migration_test_1760758803.backup.20251018034003/posts/sample-post/body.md +5 -0
  87. data/test/tmp/migration_test_1760758803.backup.20251018034003/posts/sample-post/image.jpg +2 -0
  88. data/test/tmp/migration_test_1760758803.backup.20251018034003/posts/sample-post.yml +7 -0
  89. data/test/tmp/migration_test_1760758869.backup.20251018034109/migration_test_1760758869/posts/sample-post/assets/body.md +5 -0
  90. data/test/tmp/migration_test_1760758869.backup.20251018034109/migration_test_1760758869/posts/sample-post/assets/image.jpg +2 -0
  91. data/test/tmp/migration_test_1760758869.backup.20251018034109/migration_test_1760758869/posts/sample-post/attributes.yml +2 -0
  92. data/test/tmp/migration_test_1760758869.backup.20251018034109/posts/sample-post/assets/body.md +5 -0
  93. data/test/tmp/migration_test_1760758869.backup.20251018034109/posts/sample-post/assets/image.jpg +2 -0
  94. data/test/tmp/migration_test_1760758869.backup.20251018034109/posts/sample-post/attributes.yml +2 -0
  95. data/test/tmp/migration_test_1760758920.backup.20251018034200/migration_test_1760758920/posts/sample-post/assets/body.md +5 -0
  96. data/test/tmp/migration_test_1760758920.backup.20251018034200/migration_test_1760758920/posts/sample-post/assets/image.jpg +2 -0
  97. data/test/tmp/migration_test_1760758920.backup.20251018034200/migration_test_1760758920/posts/sample-post/attributes.yml +2 -0
  98. data/test/tmp/migration_test_1760758920.backup.20251018034200/posts/sample-post/assets/body.md +5 -0
  99. data/test/tmp/migration_test_1760758920.backup.20251018034200/posts/sample-post/assets/image.jpg +2 -0
  100. data/test/tmp/migration_test_1760758920.backup.20251018034200/posts/sample-post/attributes.yml +2 -0
  101. data/test/tmp/migration_test_1760824728.backup.20251018215848/migration_test_1760824728/posts/assets-only/assets/test.txt +1 -0
  102. data/test/tmp/migration_test_1760824728.backup.20251018215848/migration_test_1760824728/posts/sample-post/assets/body.md +5 -0
  103. data/test/tmp/migration_test_1760824728.backup.20251018215848/migration_test_1760824728/posts/sample-post/assets/image.jpg +2 -0
  104. data/test/tmp/migration_test_1760824728.backup.20251018215848/migration_test_1760824728/posts/sample-post/attributes.yml +2 -0
  105. data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/assets-only/assets/test.txt +1 -0
  106. data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/sample-post/assets/body.md +5 -0
  107. data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/sample-post/assets/image.jpg +2 -0
  108. data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/sample-post/attributes.yml +2 -0
  109. data/test/tmp/migration_test_1760844153.backup.20251019032233/migration_test_1760844153/posts/assets-only/assets/test.txt +1 -0
  110. data/test/tmp/migration_test_1760844153.backup.20251019032233/migration_test_1760844153/posts/sample-post/assets/body.md +5 -0
  111. data/test/tmp/migration_test_1760844153.backup.20251019032233/migration_test_1760844153/posts/sample-post/assets/image.jpg +2 -0
  112. data/test/tmp/migration_test_1760844153.backup.20251019032233/migration_test_1760844153/posts/sample-post/attributes.yml +2 -0
  113. data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/assets-only/assets/test.txt +1 -0
  114. data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/sample-post/assets/body.md +5 -0
  115. data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/sample-post/assets/image.jpg +2 -0
  116. data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/sample-post/attributes.yml +2 -0
  117. data/test/tmp/migration_test_1760940939.backup.20251020061539/migration_test_1760940939/posts/assets-only/assets/test.txt +1 -0
  118. data/test/tmp/migration_test_1760940939.backup.20251020061539/migration_test_1760940939/posts/sample-post/assets/body.md +5 -0
  119. data/test/tmp/migration_test_1760940939.backup.20251020061539/migration_test_1760940939/posts/sample-post/assets/image.jpg +2 -0
  120. data/test/tmp/migration_test_1760940939.backup.20251020061539/migration_test_1760940939/posts/sample-post/attributes.yml +2 -0
  121. data/test/tmp/migration_test_1760940939.backup.20251020061539/posts/assets-only/assets/test.txt +1 -0
  122. data/test/tmp/migration_test_1760940939.backup.20251020061539/posts/sample-post/assets/body.md +5 -0
  123. data/test/tmp/migration_test_1760940939.backup.20251020061539/posts/sample-post/assets/image.jpg +2 -0
  124. data/test/tmp/migration_test_1760940939.backup.20251020061539/posts/sample-post/attributes.yml +2 -0
  125. data/test/tmp/migration_test_1760941048.backup.20251020061728/migration_test_1760941048/posts/assets-only/assets/test.txt +1 -0
  126. data/test/tmp/migration_test_1760941048.backup.20251020061728/migration_test_1760941048/posts/sample-post/assets/body.md +5 -0
  127. data/test/tmp/migration_test_1760941048.backup.20251020061728/migration_test_1760941048/posts/sample-post/assets/image.jpg +2 -0
  128. data/test/tmp/migration_test_1760941048.backup.20251020061728/migration_test_1760941048/posts/sample-post/attributes.yml +2 -0
  129. data/test/tmp/migration_test_1760941048.backup.20251020061728/posts/assets-only/assets/test.txt +1 -0
  130. data/test/tmp/migration_test_1760941048.backup.20251020061728/posts/sample-post/assets/body.md +5 -0
  131. data/test/tmp/migration_test_1760941048.backup.20251020061728/posts/sample-post/assets/image.jpg +2 -0
  132. data/test/tmp/migration_test_1760941048.backup.20251020061728/posts/sample-post/attributes.yml +2 -0
  133. data/test/tmp/new_structure_test_1760746452/mixed/test-json.json +5 -0
  134. data/test/tmp/new_structure_test_1760746452/mixed/test-yaml.yml +3 -0
  135. data/test/tmp/new_structure_test_1760746452/posts/metadata-only.yml +7 -0
  136. data/test/tmp/new_structure_test_1760746452/posts/nested-test/subdirectory/image.png +2 -0
  137. data/test/tmp/new_structure_test_1760746452/posts/nested-test.yml +7 -0
  138. data/test/tmp/new_structure_test_1760746452/posts/sample-post/body.md +5 -0
  139. data/test/tmp/new_structure_test_1760746452/posts/sample-post/image.jpg +2 -0
  140. data/test/tmp/new_structure_test_1760746452/posts/sample-post.yml +7 -0
  141. data/test/unit/asset_test.rb +90 -0
  142. data/test/unit/collection_test.rb +127 -0
  143. data/test/unit/migrator_test.rb +209 -0
  144. data/test/unit/node_test.rb +138 -0
  145. metadata +127 -19
  146. data/public/api/ro/pages/about/index.json +0 -60
  147. /data/public/ro/nerd/{fastest-possible-embeddings/attributes.yml → fastest-possible-embeddings.yml} +0 -0
  148. /data/public/ro/nerd/{ima/attributes.yml → ima.yml} +0 -0
  149. /data/public/ro/nerd/{index/attributes.yml → index.yml} +0 -0
  150. /data/public/ro/pages/{contact/attributes.yml → contact.yml} +0 -0
  151. /data/public/ro/pages/{cv/attributes.yml → cv.yml} +0 -0
  152. /data/public/ro/pages/{disco/attributes.yml → disco.yml} +0 -0
  153. /data/public/ro/pages/{index/attributes.yml → index.yml} +0 -0
  154. /data/public/ro/pages/{jess/attributes.yml → jess.yml} +0 -0
  155. /data/public/ro/pages/{now/attributes.yml → now.yml} +0 -0
  156. /data/public/ro/posts/{almost-died-in-an-ice-cave/attributes.yml → almost-died-in-an-ice-cave.yml} +0 -0
  157. /data/public/ro/posts/{facebook-and-global-extremism/attributes.yml → facebook-and-global-extremism.yml} +0 -0
  158. /data/public/ro/posts/{lemmings-considered-harmful/attributes.yml → lemmings-considered-harmful.yml} +0 -0
  159. /data/public/ro/posts/{lost-in-the-desert/attributes.yml → lost-in-the-desert.yml} +0 -0
  160. /data/public/ro/posts/{mission/attributes.yml → mission.yml} +0 -0
  161. /data/public/ro/posts/{return-your-laptop/attributes.yml → return-your-laptop.yml} +0 -0
@@ -0,0 +1,204 @@
1
+ module Ro
2
+ class Script::Migrate
3
+ def self.run!(script:)
4
+ new(script: script).run!
5
+ end
6
+
7
+ attr_accessor :script
8
+
9
+ def initialize(script:)
10
+ @script = script
11
+ end
12
+
13
+ def run!
14
+ parse_options!
15
+
16
+ root_path = get_root_path
17
+
18
+ show_banner(root_path)
19
+
20
+ migrator = Ro::Migrator.new(root_path, @options)
21
+
22
+ validate_and_report(migrator)
23
+
24
+ return if @options[:dry_run]
25
+
26
+ confirm_unless_forced!
27
+
28
+ execute_migration(migrator)
29
+ end
30
+
31
+ private
32
+
33
+ def parse_options!
34
+ @options = {
35
+ dry_run: false,
36
+ backup: true,
37
+ verbose: false,
38
+ force: false
39
+ }
40
+
41
+ # Use ARGV directly because the Ro.script DSL may have consumed flags
42
+ # We need to find 'migrate' in ARGV and parse everything after it
43
+ argv = []
44
+ found_migrate = false
45
+ ARGV.each do |arg|
46
+ if arg == 'migrate'
47
+ found_migrate = true
48
+ next
49
+ end
50
+ argv << arg if found_migrate
51
+ end
52
+
53
+ while argv.any?
54
+ arg = argv.shift
55
+ case arg
56
+ when '-d', '--dry-run'
57
+ @options[:dry_run] = true
58
+ @options[:verbose] = true
59
+ when '-b', '--backup'
60
+ @options[:backup] = true
61
+ when '--no-backup'
62
+ @options[:backup] = false
63
+ when '-v', '--verbose'
64
+ @options[:verbose] = true
65
+ when '-f', '--force'
66
+ @options[:force] = true
67
+ when '-h', '--help'
68
+ show_help
69
+ exit 0
70
+ else
71
+ argv.unshift(arg)
72
+ break
73
+ end
74
+ end
75
+
76
+ @script.argv.replace(argv)
77
+ end
78
+
79
+ def get_root_path
80
+ if @script.argv.any?
81
+ Pathname.new(@script.argv.first).expand_path
82
+ else
83
+ Ro.config.root.expand
84
+ end
85
+ end
86
+
87
+ def show_banner(root_path)
88
+ puts "Ro Asset Structure Migration Tool"
89
+ puts "=" * 50
90
+ puts "Root: #{root_path}"
91
+ puts "Options: #{@options.inspect}" if @options[:verbose]
92
+ puts ""
93
+ end
94
+
95
+ def validate_and_report(migrator)
96
+ puts "Validating structure..."
97
+ validation = migrator.validate
98
+
99
+ puts "Collections found: #{validation[:collections].size}"
100
+ puts "Old structure nodes: #{validation[:old_nodes].size}"
101
+ puts "New structure nodes: #{validation[:new_nodes].size}"
102
+ puts ""
103
+
104
+ if validation[:old_nodes].empty?
105
+ puts "✓ No old structure nodes found - migration not needed"
106
+ exit 0
107
+ end
108
+
109
+ if validation[:has_new_structure]
110
+ if @options[:force]
111
+ puts "⚠ Warning: Both old and new structures detected!"
112
+ puts "Proceeding with partial migration (--force enabled)..."
113
+ puts ""
114
+ else
115
+ puts "⚠ Warning: Both old and new structures detected!"
116
+ puts "This may indicate a partial migration."
117
+ puts "Use --force to proceed anyway, or check your data first."
118
+ exit 1
119
+ end
120
+ end
121
+
122
+ if @options[:dry_run] || @options[:verbose]
123
+ show_preview(migrator)
124
+ end
125
+
126
+ if @options[:dry_run]
127
+ puts "✓ Dry run complete - no changes made"
128
+ exit 0
129
+ end
130
+ end
131
+
132
+ def show_preview(migrator)
133
+ puts "Migration plan:"
134
+ puts "-" * 50
135
+ plan = migrator.preview
136
+ plan.each_with_index do |step, i|
137
+ puts "\n#{i + 1}. #{step[:collection]}/#{step[:node_id]}"
138
+ step[:actions].each do |action|
139
+ puts " - #{action}"
140
+ end
141
+ end
142
+ puts ""
143
+ end
144
+
145
+ def confirm_unless_forced!
146
+ return if @options[:force]
147
+
148
+ print "Proceed with migration? [y/N] "
149
+ response = STDIN.gets.chomp
150
+ unless response.downcase == 'y'
151
+ puts "Migration cancelled"
152
+ exit 0
153
+ end
154
+ end
155
+
156
+ def execute_migration(migrator)
157
+ puts "\nStarting migration..."
158
+ result = migrator.migrate
159
+
160
+ if result[:success]
161
+ puts "\n✓ Migration complete!"
162
+ puts " Nodes migrated: #{result[:nodes_migrated]}"
163
+ puts " Collections migrated: #{result[:collections_migrated]}"
164
+
165
+ if @options[:backup]
166
+ puts "\n Backup created - rollback available if needed"
167
+ end
168
+ else
169
+ puts "\n✗ Migration failed"
170
+ exit 1
171
+ end
172
+ end
173
+
174
+ def show_help
175
+ puts <<~HELP
176
+ Usage: ro migrate [options] [ROOT_PATH]
177
+
178
+ Migrates Ro assets from old structure to new simplified structure
179
+
180
+ Options:
181
+ -d, --dry-run Preview migration without making changes
182
+ -b, --[no-]backup Create backup before migrating (default: true)
183
+ -v, --verbose Show detailed progress
184
+ -f, --force Force migration even if new structure detected
185
+ -h, --help Show this help message
186
+
187
+ Examples:
188
+ # Preview migration
189
+ ro migrate --dry-run
190
+
191
+ # Migrate with backup (default)
192
+ ro migrate
193
+
194
+ # Migrate without backup
195
+ ro migrate --no-backup
196
+
197
+ # Force migration in mixed structure
198
+ ro migrate --force
199
+
200
+ See MIGRATION.md for more details.
201
+ HELP
202
+ end
203
+ end
204
+ end
@@ -9,7 +9,7 @@ module Ro
9
9
  def initialize(script:)
10
10
  @script = script
11
11
 
12
- @port = @script.opts.fetch(:port)
12
+ @port = @script.options.fetch(:port, Ro.defaults.port)
13
13
  end
14
14
 
15
15
  def run!
data/lib/ro.rb CHANGED
@@ -77,6 +77,7 @@ module Ro
77
77
  collection/list.rb
78
78
  node.rb
79
79
  asset.rb
80
+ migrator.rb
80
81
  ]
81
82
 
82
83
  if defined?(ActiveModel)