samagotchi 0.157.0 → 0.158.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +27 -1
- data/README.md +1 -1
- data/bin/chi +3 -1
- data/docs/architecture.md +7 -6
- data/docs/bundle-authoring.md +5 -5
- data/docs/bundles.md +2 -1
- data/docs/cli.md +21 -17
- data/docs/configuration.md +7 -5
- data/docs/context.md +5 -3
- data/docs/guardrails.md +8 -7
- data/docs/hooks.md +44 -80
- data/docs/internals/bridge.md +1 -1
- data/docs/internals/bundles.md +47 -34
- data/docs/internals/engine.md +7 -5
- data/docs/memory.md +39 -9
- data/docs/plugins.md +13 -9
- data/docs/releasing.md +6 -5
- data/docs/sessions.md +5 -5
- data/docs/testing.md +4 -4
- data/lib/samagotchi/bridge/card_places.rb +5 -13
- data/lib/samagotchi/bridge/turn_accumulator.rb +4 -1
- data/lib/samagotchi/bridge/turn_drawings.rb +7 -1
- data/lib/samagotchi/bundle_command/check.rb +23 -10
- data/lib/samagotchi/bundle_command/installed_check.rb +13 -2
- data/lib/samagotchi/bundle_command/try/run.rb +4 -1
- data/lib/samagotchi/bundle_command.rb +15 -43
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +4 -4
- data/lib/samagotchi/bundles/system/memory_guide.md +6 -6
- data/lib/samagotchi/bundles/system/self_map.md +1 -1
- data/lib/samagotchi/context_command.rb +7 -5
- data/lib/samagotchi/context_sources.rb +4 -2
- data/lib/samagotchi/engine.rb +1 -6
- data/lib/samagotchi/extension_load.rb +20 -41
- data/lib/samagotchi/guardrails/load_failures.rb +3 -2
- data/lib/samagotchi/guardrails/path_resolve.rb +11 -0
- data/lib/samagotchi/guardrails/review_paths.rb +1 -5
- data/lib/samagotchi/hook_routes.rb +2 -2
- data/lib/samagotchi/hooks.rb +2 -3
- data/lib/samagotchi/memory_bundle/asset_installer.rb +39 -119
- data/lib/samagotchi/memory_bundle/builder.rb +10 -34
- data/lib/samagotchi/memory_bundle/bundle_hook.rb +6 -31
- data/lib/samagotchi/memory_bundle/installed_bundle.rb +1 -1
- data/lib/samagotchi/memory_bundle/installed_copy.rb +11 -18
- data/lib/samagotchi/memory_bundle/installed_manifest.rb +2 -16
- data/lib/samagotchi/memory_bundle/installer.rb +32 -14
- data/lib/samagotchi/memory_bundle/manifest.rb +37 -54
- data/lib/samagotchi/memory_bundle/provenance.rb +11 -51
- data/lib/samagotchi/memory_bundle/remerge.rb +7 -3
- data/lib/samagotchi/memory_bundle/shas.rb +5 -8
- data/lib/samagotchi/memory_bundle/shipped_update.rb +30 -35
- data/lib/samagotchi/memory_bundle/status.rb +12 -10
- data/lib/samagotchi/memory_bundle/system_bundle.rb +16 -5
- data/lib/samagotchi/memory_bundle/trash.rb +7 -12
- data/lib/samagotchi/memory_orphans.rb +4 -4
- data/lib/samagotchi/memory_paths.rb +102 -13
- data/lib/samagotchi/plugin/api.rb +1 -4
- data/lib/samagotchi/plugin/loader.rb +42 -5
- data/lib/samagotchi/project_command.rb +167 -0
- data/lib/samagotchi/project_init.rb +69 -0
- data/lib/samagotchi/project_scope.rb +6 -4
- data/lib/samagotchi/prompt_files.rb +10 -7
- data/lib/samagotchi/recap_place.rb +3 -4
- data/lib/samagotchi/run_options.rb +1 -0
- data/lib/samagotchi/session.rb +44 -18
- data/lib/samagotchi/session_access/contract.rb +1 -1
- data/lib/samagotchi/session_field.rb +5 -4
- data/lib/samagotchi/steer.rb +10 -0
- data/lib/samagotchi/system_prompt.rb +20 -11
- data/lib/samagotchi/terminal_ui/join_replay.rb +6 -1
- data/lib/samagotchi/testing/bundle.rb +5 -12
- data/lib/samagotchi/testing/install.rb +5 -5
- data/lib/samagotchi/testing/recorder.rb +1 -1
- data/lib/samagotchi/testing.rb +1 -1
- data/lib/samagotchi/tool_declarations.rb +1 -1
- data/lib/samagotchi/toolkit_binding.rb +1 -4
- data/lib/samagotchi/toolkit_command.rb +1 -4
- data/lib/samagotchi/tools/delegate.rb +12 -8
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +35 -0
- data/lib/samagotchi/web/project_list.rb +3 -2
- data/lib/samagotchi/web/public/index.html +6 -1
- data/lib/samagotchi/web/public/project_picker.js +86 -4
- metadata +3 -2
- data/lib/samagotchi/hooks/bundle_loader.rb +0 -191
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require "fileutils"
|
|
4
4
|
require_relative "file_digest"
|
|
5
|
-
require_relative "bundle_hook"
|
|
6
5
|
require_relative "manifest"
|
|
7
6
|
require_relative "provenance"
|
|
8
7
|
require_relative "../version"
|
|
@@ -13,22 +12,25 @@ module Samagotchi
|
|
|
13
12
|
|
|
14
13
|
# The install of a bundle's parts that live in its own dir
|
|
15
14
|
# (<bundles>/<name>/, Provenance#bundle_dir) rather than in the
|
|
16
|
-
# memories dir:
|
|
17
|
-
#
|
|
15
|
+
# memories dir: guardrails/*.yml, the plugin file and scripts/*. Each
|
|
16
|
+
# replaces what an earlier version installed. An older chi's record
|
|
17
|
+
# with class-file hooks has them removed (#retire_hooks): chi no longer
|
|
18
|
+
# loads them, and the Installer refuses a source that has any. Installer
|
|
18
19
|
# runs it after the memories; it adds to the installer's results and
|
|
19
20
|
# warnings, and #run returns what the record (Provenance#write) keeps.
|
|
20
21
|
# A dev install (+dev+, `chi bundle install --dev`) links each file to
|
|
21
22
|
# the source instead of copying it, and warns of no checksum mismatch.
|
|
22
23
|
# An edited file it replaces or removes is copied to the trash first,
|
|
23
|
-
# under its subfolder (Trash#
|
|
24
|
+
# under its subfolder (Trash#copy_edits); a link never is.
|
|
24
25
|
class AssetInstaller
|
|
25
26
|
# What was installed: the paths Provenance#write records (each
|
|
26
27
|
# empty / nil on a dry run).
|
|
27
|
-
#
|
|
28
|
-
# hook_files, guardrail_files, script_files
|
|
29
|
-
# basename => installed path
|
|
28
|
+
# guardrail_files, script_files basename => installed path
|
|
30
29
|
# plugin_file the installed plugin file, nil without one
|
|
31
|
-
Installed = Data.define(:
|
|
30
|
+
Installed = Data.define(:guardrail_files, :plugin_file, :script_files)
|
|
31
|
+
|
|
32
|
+
# The retire's summary words (Installer#summary): why the hooks went.
|
|
33
|
+
RETIRED = "chi no longer loads class-file bundle hooks"
|
|
32
34
|
|
|
33
35
|
# @param source_dir [String] the normalized bundle source
|
|
34
36
|
# @param manifest [Manifest, nil] nil for a non-strict install without one
|
|
@@ -61,18 +63,16 @@ module Samagotchi
|
|
|
61
63
|
# @raise [Installer::InstallError] the manifest names a plugin or
|
|
62
64
|
# script the bundle doesn't have
|
|
63
65
|
def run
|
|
64
|
-
install_hooks
|
|
65
|
-
prune_dropped_hooks if (@upgrade || (@dev && @existing&.dev)) && @existing && !@dry_run
|
|
66
|
-
verify_hook_checksums if @manifest && @strict && !@dev
|
|
67
66
|
incoming_rules = Dir.glob(File.join(@bundle_dir, "guardrails", "*.yml")).sort
|
|
68
67
|
.reject { |src| linked_out?(src, "guardrails/#{File.basename(src)}") }
|
|
69
68
|
guardrail_files = install_guardrails(incoming_rules)
|
|
70
69
|
plugin_file = install_plugin
|
|
71
70
|
script_files = install_scripts
|
|
72
|
-
|
|
71
|
+
# After the plugin and scripts, which raise for a file the manifest
|
|
72
|
+
# names and the bundle lacks: a refused install leaves hooks/ as it was.
|
|
73
|
+
retire_hooks
|
|
73
74
|
warn_rules_requires_chi(incoming_rules)
|
|
74
|
-
Installed.new(
|
|
75
|
-
plugin_file: plugin_file, script_files: script_files)
|
|
75
|
+
Installed.new(guardrail_files: guardrail_files, plugin_file: plugin_file, script_files: script_files)
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
# Whether the bundle file +src+ reads through a symlink: it is one, or
|
|
@@ -91,99 +91,28 @@ module Samagotchi
|
|
|
91
91
|
|
|
92
92
|
private
|
|
93
93
|
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
end
|
|
109
|
-
hook_entries.keys.each do |basename|
|
|
110
|
-
next unless basename.is_a?(String) && !basename.empty?
|
|
111
|
-
|
|
112
|
-
src = File.join(@bundle_dir, "hooks", basename)
|
|
113
|
-
# A linked hook isn't the bundle's: not installed, counted or recorded.
|
|
114
|
-
next hook_entries.delete(basename) if (File.symlink?(src) || File.exist?(src)) && linked_out?(src, "hooks/#{basename}")
|
|
115
|
-
|
|
116
|
-
@bundle_hooks << basename
|
|
117
|
-
install_hook(basename, src) if File.exist?(src)
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
# One listed hook; an upgrade overwrites a local edit with a warning.
|
|
122
|
-
def install_hook(basename, src)
|
|
123
|
-
dest = File.join(@provenance.hooks_dir, basename)
|
|
124
|
-
result = result_for(src, dest, updated: @upgrade && @existing)
|
|
125
|
-
return @results[basename] = planned(result) if @dry_run
|
|
126
|
-
|
|
127
|
-
prev_sha = @existing&.hooks&.dig(basename)&.sha256
|
|
128
|
-
if @upgrade && File.exist?(dest) && !@force && !Provenance.recorded_sha(prev_sha).empty? && !Provenance.sha_matches?(dest, prev_sha)
|
|
129
|
-
@warnings << "Hook #{basename} was locally modified; overwriting"
|
|
94
|
+
# The hooks/ an older chi installed (class-file hooks, which no longer
|
|
95
|
+
# load): every file in it goes, an edited one (or one the record
|
|
96
|
+
# doesn't know) copied to the trash first, a dev link never (it points
|
|
97
|
+
# at the source, which stays). Each is a :removed result (a dry run's
|
|
98
|
+
# :would_remove, through #planned) with the RETIRED note; an empty
|
|
99
|
+
# hooks/ dir goes without a word. The record keeps no hooks
|
|
100
|
+
# (Provenance#write).
|
|
101
|
+
def retire_hooks
|
|
102
|
+
dir = @provenance.hooks_dir
|
|
103
|
+
return unless File.directory?(dir) || File.symlink?(dir)
|
|
104
|
+
|
|
105
|
+
files = File.symlink?(dir) ? [] : Dir.glob("**/*", File::FNM_DOTMATCH, base: dir).sort.reject { |f| File.directory?(File.join(dir, f)) }
|
|
106
|
+
files.each do |file|
|
|
107
|
+
@results["hooks/#{file}"] = @dry_run ? planned(retired) : retired
|
|
130
108
|
end
|
|
109
|
+
return if @dry_run
|
|
131
110
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
place(src, dest)
|
|
135
|
-
@results[basename] = result
|
|
136
|
-
@hook_files[basename] = dest
|
|
111
|
+
@trash.copy_edits(dir, "hooks", @existing&.hooks&.transform_values(&:sha256))
|
|
112
|
+
FileUtils.rm_rf(dir)
|
|
137
113
|
end
|
|
138
114
|
|
|
139
|
-
|
|
140
|
-
def install_unlisted_hooks
|
|
141
|
-
Dir.glob(File.join(@bundle_dir, "hooks", "*.rb")).each do |src|
|
|
142
|
-
basename = File.basename(src)
|
|
143
|
-
next if linked_out?(src, "hooks/#{basename}")
|
|
144
|
-
|
|
145
|
-
@bundle_hooks << basename
|
|
146
|
-
if @dry_run
|
|
147
|
-
@results[basename] = Installer::FileResult.new(word: :would_install)
|
|
148
|
-
else
|
|
149
|
-
FileUtils.mkdir_p(@provenance.hooks_dir)
|
|
150
|
-
dest = File.join(@provenance.hooks_dir, basename)
|
|
151
|
-
@trash.copy_edit(dest, as: "hooks/#{basename}", recorded: @existing&.hooks&.dig(basename)&.sha256, incoming: src)
|
|
152
|
-
place(src, dest)
|
|
153
|
-
@results[basename] = Installer::FileResult.new(word: :installed)
|
|
154
|
-
@hook_files[basename] = dest
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
# An upgrade removes the hooks the previous version shipped and this
|
|
160
|
-
# one doesn't (a local file too, with a warning; a dev link silently).
|
|
161
|
-
def prune_dropped_hooks
|
|
162
|
-
@existing.hooks.each_key do |old_key_str|
|
|
163
|
-
next if @bundle_hooks.include?(old_key_str)
|
|
164
|
-
|
|
165
|
-
old_dest = File.join(@provenance.hooks_dir, old_key_str)
|
|
166
|
-
if File.exist?(old_dest) && !@force && !File.symlink?(old_dest)
|
|
167
|
-
@warnings << "Bundle no longer includes hook #{old_key_str} but local file exists — removing"
|
|
168
|
-
end
|
|
169
|
-
@trash.copy_edit(old_dest, as: "hooks/#{old_key_str}", recorded: @existing.hooks[old_key_str]&.sha256)
|
|
170
|
-
FileUtils.rm_f(old_dest)
|
|
171
|
-
end
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
# Strict mode: a hook whose sha256 differs from the one its manifest
|
|
175
|
-
# declares warns (as verify_memory_checksums does). The bundle's file is
|
|
176
|
-
# checked, the one the install copies, so a dry run checks what it
|
|
177
|
-
# would install, not the old hook in place.
|
|
178
|
-
def verify_hook_checksums
|
|
179
|
-
@bundle_hooks.each do |basename|
|
|
180
|
-
next if @conflicts.key?(basename)
|
|
181
|
-
|
|
182
|
-
warning = FileDigest.mismatch("hook #{basename}", File.join(@bundle_dir, "hooks", basename),
|
|
183
|
-
@manifest.checksum_for_hook(basename))
|
|
184
|
-
@warnings << warning if warning
|
|
185
|
-
end
|
|
186
|
-
end
|
|
115
|
+
def retired = Installer::FileResult.new(word: :removed, note: RETIRED)
|
|
187
116
|
|
|
188
117
|
# The bundle's guardrails/*.yml into <bundle_dir>/guardrails/: the
|
|
189
118
|
# set replaces what an earlier version installed. Same bytes as
|
|
@@ -289,22 +218,24 @@ module Samagotchi
|
|
|
289
218
|
|
|
290
219
|
# What the real run reports for +src+ over +dest+: up to date when the
|
|
291
220
|
# bytes are the same, else updated (+updated+: an upgrade over an
|
|
292
|
-
# install) or installed. A dry run records #planned of it, so the two
|
|
221
|
+
# install, of a file that is there) or installed. A dry run records #planned of it, so the two
|
|
293
222
|
# can't drift.
|
|
294
223
|
def result_for(src, dest, updated:)
|
|
295
224
|
word = if File.file?(dest) && FileUtils.identical?(src, dest) then :up_to_date
|
|
296
|
-
elsif updated then :updated
|
|
225
|
+
elsif updated && File.exist?(dest) then :updated
|
|
297
226
|
else :installed
|
|
298
227
|
end
|
|
299
228
|
Installer::FileResult.new(word: word)
|
|
300
229
|
end
|
|
301
230
|
|
|
302
|
-
# A dry run's word for a result: would_install, would_update,
|
|
303
|
-
# up to date result as it is (nothing would be
|
|
231
|
+
# A dry run's word for a result: would_install, would_update,
|
|
232
|
+
# would_remove, or the up to date result as it is (nothing would be
|
|
233
|
+
# written).
|
|
304
234
|
def planned(result)
|
|
305
235
|
case result.word
|
|
306
236
|
when :installed then result.with(word: :would_install)
|
|
307
237
|
when :updated then result.with(word: :would_update)
|
|
238
|
+
when :removed then result.with(word: :would_remove)
|
|
308
239
|
else result
|
|
309
240
|
end
|
|
310
241
|
end
|
|
@@ -333,21 +264,10 @@ module Samagotchi
|
|
|
333
264
|
true
|
|
334
265
|
end
|
|
335
266
|
|
|
336
|
-
# The hook loader skips every hook of a bundle whose requires_chi this
|
|
337
|
-
# chi doesn't meet (Hooks::BundleLoader); say so at install, as
|
|
338
|
-
# install_plugin does for the plugin.
|
|
339
|
-
def warn_hooks_requires_chi
|
|
340
|
-
return if @manifest.nil? || @bundle_hooks.empty?
|
|
341
|
-
|
|
342
|
-
if (failure = Manifest.requires_chi_failure(@manifest.requires_chi, @chi_version))
|
|
343
|
-
@warnings << "Bundle #{@name}: its hooks won't load: #{failure}"
|
|
344
|
-
end
|
|
345
|
-
end
|
|
346
|
-
|
|
347
267
|
# GuardrailWiring#bundle_rules doesn't read the rules of a bundle whose
|
|
348
268
|
# requires_chi this chi doesn't meet, and a session that never loaded
|
|
349
269
|
# them records a required load failure (the gate refuses guarded calls
|
|
350
|
-
# until chi is updated): say so at install, as
|
|
270
|
+
# until chi is updated): say so at install, as install_plugin does.
|
|
351
271
|
def warn_rules_requires_chi(rule_files)
|
|
352
272
|
return if @manifest.nil? || rule_files.empty?
|
|
353
273
|
|
|
@@ -4,7 +4,6 @@ require "digest"
|
|
|
4
4
|
require_relative "file_digest"
|
|
5
5
|
require "fileutils"
|
|
6
6
|
require "tmpdir"
|
|
7
|
-
require_relative "bundle_hook"
|
|
8
7
|
require_relative "manifest"
|
|
9
8
|
require_relative "placeholder"
|
|
10
9
|
require_relative "../memory_paths"
|
|
@@ -12,7 +11,9 @@ require_relative "../model_overlay"
|
|
|
12
11
|
|
|
13
12
|
module Samagotchi
|
|
14
13
|
module MemoryBundle
|
|
15
|
-
# Builds local memories and installed
|
|
14
|
+
# Builds local memories and an installed bundle's rules, plugin and
|
|
15
|
+
# scripts into a shareable bundle (class-file hooks are retired: an older
|
|
16
|
+
# record's hooks/ isn't built).
|
|
16
17
|
#
|
|
17
18
|
# Inverse of Installer: reads from the resolved scope directory,
|
|
18
19
|
# computes SHA256 checksums, writes manifest.yml via Manifest.write,
|
|
@@ -27,11 +28,10 @@ module Samagotchi
|
|
|
27
28
|
# record and its dir), else from the scope dir:
|
|
28
29
|
# record the InstalledBundle, nil when none is installed (or
|
|
29
30
|
# it doesn't read)
|
|
30
|
-
# hooks basename => BundleHook; hook_files basename => path
|
|
31
31
|
# rules guardrails/*.yml paths
|
|
32
32
|
# plugin the installed plugin file, nil without one
|
|
33
33
|
# scripts file name => path; context_providers, block_renderers the parsed lists
|
|
34
|
-
InstalledParts = Data.define(:record, :
|
|
34
|
+
InstalledParts = Data.define(:record, :rules, :plugin, :scripts, :context_providers, :block_renderers)
|
|
35
35
|
|
|
36
36
|
attr_reader :built_files, :warnings, :placeholder_warnings, :out_path, :staging_dir
|
|
37
37
|
|
|
@@ -138,12 +138,10 @@ module Samagotchi
|
|
|
138
138
|
end
|
|
139
139
|
end
|
|
140
140
|
|
|
141
|
-
# The installed bundle's
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
#
|
|
145
|
-
# record, the scope dir's hooks/*.rb and guardrails/*.yml (building
|
|
146
|
-
# from a raw bundle dir that is also the memories dir).
|
|
141
|
+
# The installed bundle's guardrail rules, plugin, scripts, context
|
|
142
|
+
# providers and block renderers (its InstalledManifest's). Without
|
|
143
|
+
# rules in a record, the scope dir's guardrails/*.yml (building from a
|
|
144
|
+
# raw bundle dir that is also the memories dir).
|
|
147
145
|
# @return [InstalledParts]
|
|
148
146
|
def installed_parts(name, target_dir)
|
|
149
147
|
prov = nil
|
|
@@ -156,23 +154,6 @@ module Samagotchi
|
|
|
156
154
|
record = nil
|
|
157
155
|
end
|
|
158
156
|
|
|
159
|
-
hooks = {}
|
|
160
|
-
hook_files = {}
|
|
161
|
-
if record&.hooks&.any?
|
|
162
|
-
record.hooks.each do |basename, hook|
|
|
163
|
-
src = File.join(prov.hooks_dir, basename)
|
|
164
|
-
next unless File.exist?(src)
|
|
165
|
-
|
|
166
|
-
hooks[basename] = hook.sha256.empty? ? hook.with(sha256: BundleHook.of_file(src).sha256) : hook
|
|
167
|
-
hook_files[basename] = src
|
|
168
|
-
end
|
|
169
|
-
else
|
|
170
|
-
Dir.glob(File.join(target_dir, "hooks", "*.rb")).sort.each do |src|
|
|
171
|
-
hooks[File.basename(src)] = BundleHook.of_file(src)
|
|
172
|
-
hook_files[File.basename(src)] = src
|
|
173
|
-
end
|
|
174
|
-
end
|
|
175
|
-
|
|
176
157
|
rules_dir = record&.guardrails&.any? ? prov.guardrails_dir : File.join(target_dir, "guardrails")
|
|
177
158
|
plugin = record ? prov.plugin_path(record) : nil
|
|
178
159
|
manifest = record&.manifest
|
|
@@ -181,7 +162,7 @@ module Samagotchi
|
|
|
181
162
|
raise BuildError, "the installed #{name}'s record: #{problem}" if problem
|
|
182
163
|
|
|
183
164
|
InstalledParts.new(
|
|
184
|
-
record: record,
|
|
165
|
+
record: record,
|
|
185
166
|
rules: Dir.exist?(rules_dir) ? Dir.glob(File.join(rules_dir, "*.yml")).sort : [],
|
|
186
167
|
plugin: plugin && File.file?(plugin) ? plugin : nil,
|
|
187
168
|
scripts: installed_scripts(prov, record),
|
|
@@ -190,14 +171,10 @@ module Samagotchi
|
|
|
190
171
|
)
|
|
191
172
|
end
|
|
192
173
|
|
|
193
|
-
# The memories,
|
|
174
|
+
# The memories, guardrails/, the plugin and scripts/ into the
|
|
194
175
|
# staging dir, laid out as Installer reads a bundle.
|
|
195
176
|
def stage_files(staging, candidates, parts)
|
|
196
177
|
candidates.each { |src| FileUtils.cp(src, File.join(staging, File.basename(src))) }
|
|
197
|
-
unless parts.hook_files.empty?
|
|
198
|
-
FileUtils.mkdir_p(File.join(staging, "hooks"))
|
|
199
|
-
parts.hook_files.each { |basename, src| FileUtils.cp(src, File.join(staging, "hooks", basename)) }
|
|
200
|
-
end
|
|
201
178
|
unless parts.rules.empty?
|
|
202
179
|
FileUtils.mkdir_p(File.join(staging, "guardrails"))
|
|
203
180
|
parts.rules.each { |src| FileUtils.cp(src, File.join(staging, "guardrails", File.basename(src))) }
|
|
@@ -225,7 +202,6 @@ module Samagotchi
|
|
|
225
202
|
scope: @scope,
|
|
226
203
|
description: description,
|
|
227
204
|
files: files_map,
|
|
228
|
-
hooks: parts.hooks.empty? ? nil : parts.hooks,
|
|
229
205
|
trust_level: trust_level,
|
|
230
206
|
plugin: parts.plugin && { file: File.basename(parts.plugin), sha256: FileDigest.sha256(parts.plugin),
|
|
231
207
|
required: manifest&.plugin_required },
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "file_digest"
|
|
4
|
-
|
|
5
3
|
module Samagotchi
|
|
6
4
|
module MemoryBundle
|
|
7
|
-
# One hook's metadata, as
|
|
8
|
-
#
|
|
5
|
+
# One class-file hook's metadata, as an older chi's installed record
|
|
6
|
+
# (manifest.json) keeps it (chi no longer installs or loads them:
|
|
7
|
+
# Manifest::HooksRetired; it reads them to retire, uninstall and name
|
|
8
|
+
# them, and the trash selection takes #sha256): the file's sha256
|
|
9
9
|
# ("sha256:<hex>", "" when none is recorded), the events it registers
|
|
10
10
|
# for (manifest `event:` for one, `events:` a list; none: it isn't
|
|
11
|
-
# loaded), on_error (skip, log or fail_closed) and priority.
|
|
12
|
-
#
|
|
13
|
-
# defaults live.
|
|
11
|
+
# loaded), on_error (skip, log or fail_closed) and priority. The one
|
|
12
|
+
# place the defaults live.
|
|
14
13
|
class BundleHook < Data.define(:sha256, :events, :on_error, :priority)
|
|
15
14
|
DEFAULT_ON_ERROR = "skip"
|
|
16
15
|
DEFAULT_PRIORITY = 100
|
|
@@ -37,35 +36,11 @@ module Samagotchi
|
|
|
37
36
|
priority: (h["priority"] || DEFAULT_PRIORITY).to_i)
|
|
38
37
|
end
|
|
39
38
|
|
|
40
|
-
# A hook known only by its file (no declared event): its sha, the defaults.
|
|
41
|
-
# Callers pass a file that is there (FileDigest is nil for one that isn't).
|
|
42
|
-
def self.of_file(path)
|
|
43
|
-
new(sha256: FileDigest.sha256(path))
|
|
44
|
-
end
|
|
45
|
-
|
|
46
39
|
# "sha256:<hex>" from either form, "" for none.
|
|
47
40
|
def self.prefixed(sha)
|
|
48
41
|
sha = sha.to_s.strip
|
|
49
42
|
sha.empty? || sha.start_with?("sha256:") ? sha : "sha256:#{sha}"
|
|
50
43
|
end
|
|
51
|
-
|
|
52
|
-
# The sha's hex, "" when none is recorded.
|
|
53
|
-
def hex = sha256.delete_prefix("sha256:")
|
|
54
|
-
|
|
55
|
-
# Whether the hook fails closed: on_error fail_closed applies to its
|
|
56
|
-
# before_tool_call registration only.
|
|
57
|
-
def fail_closed?(event) = on_error == "fail_closed" && event.to_s == "before_tool_call"
|
|
58
|
-
|
|
59
|
-
# How `chi bundle status` / `diff` name its events: `event=x`, or
|
|
60
|
-
# `events=x,y` for more than one.
|
|
61
|
-
def events_field = events.size > 1 ? "events=#{events.join(",")}" : "event=#{events.first}"
|
|
62
|
-
|
|
63
|
-
# The string-keyed form manifest.yml and manifest.json hold: `event`
|
|
64
|
-
# for one event (as an older chi reads it), `events` for more.
|
|
65
|
-
def to_record
|
|
66
|
-
listed = events.size > 1 ? { "events" => events.dup } : { "event" => events.first.to_s }
|
|
67
|
-
{ "sha256" => sha256, **listed, "on_error" => on_error, "priority" => priority }
|
|
68
|
-
end
|
|
69
44
|
end
|
|
70
45
|
end
|
|
71
46
|
end
|
|
@@ -119,7 +119,7 @@ module Samagotchi
|
|
|
119
119
|
# recorded is gone (:missing). false with no dir.
|
|
120
120
|
def manifest_unkept? = !dir.nil? && (manifest_sha.to_s.empty? || !File.file?(kept_manifest_path))
|
|
121
121
|
|
|
122
|
-
# What its manifest says (requires_chi,
|
|
122
|
+
# What its manifest says (requires_chi, needs, providers,
|
|
123
123
|
# renderers, settings…): the one way to read manifest data of an
|
|
124
124
|
# installed bundle.
|
|
125
125
|
# @return [InstalledManifest]
|
|
@@ -3,27 +3,28 @@
|
|
|
3
3
|
require "fileutils"
|
|
4
4
|
require "tmpdir"
|
|
5
5
|
require "yaml"
|
|
6
|
-
require_relative "bundle_hook"
|
|
7
6
|
require_relative "manifest"
|
|
8
7
|
require_relative "provenance"
|
|
9
8
|
|
|
10
9
|
module Samagotchi
|
|
11
10
|
module MemoryBundle
|
|
12
|
-
# An installed bundle laid out as a source folder again: the plugin,
|
|
11
|
+
# An installed bundle laid out as a source folder again: the plugin,
|
|
13
12
|
# rules and scripts as they are installed (a dev install's links
|
|
14
13
|
# followed), the memories as their bases (the bundle's bytes, not the
|
|
15
14
|
# user's edits). A bundle's source may be gone or be a URL; this is what
|
|
16
15
|
# `chi update` can check of what really runs.
|
|
17
16
|
#
|
|
18
|
-
# manifest.yml: its files:,
|
|
19
|
-
#
|
|
20
|
-
# with no base,
|
|
21
|
-
#
|
|
17
|
+
# manifest.yml: its files:, plugin: and scripts: are the record's and
|
|
18
|
+
# the copy's (what is installed: a memory the install left out, or one
|
|
19
|
+
# with no base, isn't listed, so the check finds no missing-file for it).
|
|
20
|
+
# It has no hooks: and no hooks/, whatever the record or the kept
|
|
21
|
+
# manifest says: class-file hooks don't load (InstalledCheck names a
|
|
22
|
+
# record's once). Every other key is the kept
|
|
22
23
|
# manifest.yml's when it is read (InstalledManifest origin :kept), so the
|
|
23
24
|
# check sees its unknown keys too; else the record's mirror's.
|
|
24
25
|
#
|
|
25
26
|
# The shas are the files' own, so an edited plugin is checked as written.
|
|
26
|
-
# A plugin
|
|
27
|
+
# A plugin or script the record lists that isn't there stays
|
|
27
28
|
# listed, for the check's missing-file.
|
|
28
29
|
module InstalledCopy
|
|
29
30
|
module_function
|
|
@@ -45,7 +46,7 @@ module Samagotchi
|
|
|
45
46
|
dir: dir, name: bundle.name, version: bundle.version.to_s.empty? ? "0" : bundle.version, trust_level: manifest.trust_level,
|
|
46
47
|
files: memories(bundle, provenance, dir),
|
|
47
48
|
file_descriptions: manifest.file_descriptions,
|
|
48
|
-
|
|
49
|
+
plugin: plugin(bundle, provenance, dir),
|
|
49
50
|
scripts: copies(bundle.scripts, provenance.scripts_dir, File.join(dir, "scripts")),
|
|
50
51
|
requires_chi: manifest.requires_chi, needs: manifest.needs,
|
|
51
52
|
context_providers: manifest.context_providers, block_renderers: manifest.block_renderers,
|
|
@@ -57,7 +58,8 @@ module Samagotchi
|
|
|
57
58
|
end
|
|
58
59
|
private_class_method :write
|
|
59
60
|
|
|
60
|
-
# What is installed, which the copy's manifest.yml takes from the
|
|
61
|
+
# What is installed, which the copy's manifest.yml takes from the
|
|
62
|
+
# record (hooks: none, so the kept manifest's isn't taken either).
|
|
61
63
|
INSTALLED_KEYS = %w[name version files hooks plugin scripts].freeze
|
|
62
64
|
|
|
63
65
|
# The written manifest's INSTALLED_KEYS and +failed+ keys (the value
|
|
@@ -80,15 +82,6 @@ module Samagotchi
|
|
|
80
82
|
end
|
|
81
83
|
private_class_method :memories
|
|
82
84
|
|
|
83
|
-
# The recorded hooks with the metadata they load with (InstalledManifest#hooks).
|
|
84
|
-
def hooks(bundle, provenance, dir)
|
|
85
|
-
bundle.manifest.hooks.to_h do |file, hook|
|
|
86
|
-
sha = copy(File.join(provenance.hooks_dir, file), File.join(dir, "hooks", file), hook.sha256)
|
|
87
|
-
[file, hook.with(sha256: sha)]
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
private_class_method :hooks
|
|
91
|
-
|
|
92
85
|
def plugin(bundle, provenance, dir)
|
|
93
86
|
return nil unless bundle.plugin
|
|
94
87
|
|
|
@@ -39,9 +39,6 @@ module Samagotchi
|
|
|
39
39
|
#
|
|
40
40
|
# requires_chi, trust_level the strings as recorded, nil when none
|
|
41
41
|
# description the kept manifest's, nil from the record
|
|
42
|
-
# hooks basename => BundleHook: each recorded hook,
|
|
43
|
-
# the kept manifest's events, on_error and
|
|
44
|
-
# priority with the record's sha
|
|
45
42
|
# needs Manifest.parse_needs'
|
|
46
43
|
# script_names the scripts/ files it lists
|
|
47
44
|
# context_providers Array<ContextProviders::Provider>
|
|
@@ -61,7 +58,7 @@ module Samagotchi
|
|
|
61
58
|
# floor_keys the kept manifest's Manifest#floor_keys (key =>
|
|
62
59
|
# the first chi that read it), {} from the record
|
|
63
60
|
# origin :kept or :record
|
|
64
|
-
class InstalledManifest < Data.define(:name, :version, :requires_chi, :trust_level, :description, :
|
|
61
|
+
class InstalledManifest < Data.define(:name, :version, :requires_chi, :trust_level, :description, :needs,
|
|
65
62
|
:script_names, :context_providers, :block_renderers, :session_settings,
|
|
66
63
|
:session_setting_types, :settings, :setting_types, :file_descriptions,
|
|
67
64
|
:plugin_required, :includes, :unknown_keys, :floor_keys, :problems, :origin)
|
|
@@ -130,7 +127,6 @@ module Samagotchi
|
|
|
130
127
|
new(name: bundle.name, version: bundle.version,
|
|
131
128
|
requires_chi: pick.call("requires_chi", kept.requires_chi, mirrored.requires_chi),
|
|
132
129
|
trust_level: kept.trust_level, description: kept.description.empty? ? nil : kept.description,
|
|
133
|
-
hooks: pick.call("hooks", kept_hooks(bundle.hooks, kept.hooks), bundle.hooks),
|
|
134
130
|
needs: pick.call("needs", kept.needs, mirrored.needs),
|
|
135
131
|
script_names: pick.call("scripts", kept.scripts.keys, mirrored.script_names),
|
|
136
132
|
context_providers: pick.call("context_providers", kept.context_providers, mirrored.context_providers),
|
|
@@ -150,21 +146,11 @@ module Samagotchi
|
|
|
150
146
|
{}
|
|
151
147
|
end
|
|
152
148
|
|
|
153
|
-
# Each recorded hook (what is installed): the kept manifest's metadata
|
|
154
|
-
# for it with the record's sha; one the manifest doesn't list (a found
|
|
155
|
-
# hooks/*.rb) as recorded. A manifest hook the record lacks isn't installed.
|
|
156
|
-
def self.kept_hooks(recorded, declared)
|
|
157
|
-
recorded.to_h do |file, hook|
|
|
158
|
-
[file, declared.key?(file) ? declared[file].with(sha256: hook.sha256) : hook]
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
|
|
162
149
|
def self.from_record(bundle)
|
|
163
150
|
raw = recorded(bundle)
|
|
164
151
|
problems = {}
|
|
165
152
|
new(name: bundle.name, version: bundle.version,
|
|
166
153
|
requires_chi: raw["requires_chi"]&.to_s, trust_level: raw["trust_level"]&.to_s, description: nil,
|
|
167
|
-
hooks: bundle.hooks,
|
|
168
154
|
needs: parsed(problems, "needs", [], Manifest::ValidationError) { Manifest.parse_needs(raw["needs"]) },
|
|
169
155
|
script_names: bundle.scripts.keys,
|
|
170
156
|
context_providers: parsed(problems, "context_providers", []) { ContextProviders.parse_list(raw["context_providers"]) },
|
|
@@ -204,7 +190,7 @@ module Samagotchi
|
|
|
204
190
|
acc[key] = text unless text.empty?
|
|
205
191
|
end
|
|
206
192
|
end
|
|
207
|
-
private_class_method :deep_freeze, :kept_stat, :build, :standing, :from_kept, :floors, :
|
|
193
|
+
private_class_method :deep_freeze, :kept_stat, :build, :standing, :from_kept, :floors, :from_record, :recorded, :parsed,
|
|
208
194
|
:list, :types, :file_descriptions
|
|
209
195
|
|
|
210
196
|
# Why the kept manifest.yml wasn't read: :missing, :stale or :edited (the class
|