samagotchi 0.157.0 → 0.159.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 +47 -1
- data/README.md +1 -1
- data/bin/chi +5 -1
- data/docs/architecture.md +14 -12
- data/docs/bundle-authoring.md +5 -5
- data/docs/bundles.md +2 -1
- data/docs/cli.md +76 -17
- data/docs/configuration.md +16 -5
- data/docs/context.md +12 -4
- data/docs/guardrails.md +11 -8
- data/docs/hooks.md +47 -80
- data/docs/internals/bridge.md +2 -1
- data/docs/internals/bundles.md +51 -36
- data/docs/internals/engine.md +7 -5
- data/docs/memory.md +43 -9
- data/docs/plugins.md +13 -9
- data/docs/releasing.md +6 -5
- data/docs/scheduling.md +228 -0
- data/docs/sessions.md +30 -6
- 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/bridge.rb +26 -2
- 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 +53 -44
- 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/client_id.rb +13 -3
- data/lib/samagotchi/config.rb +12 -0
- data/lib/samagotchi/context_command.rb +7 -5
- data/lib/samagotchi/context_move.rb +74 -0
- 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/pair_boundary.rb +1 -1
- 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 +112 -211
- data/lib/samagotchi/memory_bundle/asset_state.rb +81 -0
- data/lib/samagotchi/memory_bundle/builder.rb +10 -34
- data/lib/samagotchi/memory_bundle/bundle_hook.rb +6 -31
- data/lib/samagotchi/memory_bundle/file_actions.rb +51 -0
- 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 +41 -41
- data/lib/samagotchi/memory_bundle/status.rb +30 -9
- data/lib/samagotchi/memory_bundle/system_bundle.rb +16 -5
- data/lib/samagotchi/memory_bundle/trash.rb +7 -12
- data/lib/samagotchi/memory_bundle/uninstaller.rb +10 -4
- 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 +2 -0
- data/lib/samagotchi/schedule_command.rb +252 -0
- data/lib/samagotchi/schedules.rb +948 -0
- data/lib/samagotchi/session.rb +44 -18
- data/lib/samagotchi/session_access/contract.rb +1 -1
- data/lib/samagotchi/session_access/local.rb +20 -0
- data/lib/samagotchi/session_field.rb +5 -4
- data/lib/samagotchi/session_manager/spawn.rb +25 -11
- data/lib/samagotchi/session_manager/worker_command.rb +13 -13
- data/lib/samagotchi/steer.rb +10 -0
- data/lib/samagotchi/system_prompt.rb +20 -11
- data/lib/samagotchi/terminal_ui/formatting.rb +2 -0
- 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 +44 -1
- data/lib/samagotchi/toolkit_binding.rb +1 -4
- data/lib/samagotchi/toolkit_command.rb +1 -4
- data/lib/samagotchi/tools/builtin_calls.rb +2 -0
- data/lib/samagotchi/tools/builtins.rb +17 -0
- data/lib/samagotchi/tools/cancel_schedule.rb +43 -0
- data/lib/samagotchi/tools/delegate.rb +12 -8
- data/lib/samagotchi/tools/list_schedules.rb +43 -0
- data/lib/samagotchi/tools/schedule_task.rb +39 -0
- 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/chains.js +32 -8
- data/lib/samagotchi/web/public/index.html +10 -2
- data/lib/samagotchi/web/public/info_bar.js +4 -3
- data/lib/samagotchi/web/public/project_picker.js +86 -4
- data/lib/samagotchi/web/public/turn_events.js +4 -0
- data/lib/samagotchi/web/session_hub.rb +20 -0
- data/lib/samagotchi/worker.rb +24 -6
- data/lib/samagotchi/worker_boot.rb +3 -4
- data/lib/samagotchi/worker_inbound.rb +23 -0
- data/lib/samagotchi/worker_link.rb +5 -0
- data/lib/samagotchi/worker_wakes.rb +9 -1
- metadata +10 -2
- data/lib/samagotchi/hooks/bundle_loader.rb +0 -191
|
@@ -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
|
|
@@ -22,6 +22,8 @@ module Samagotchi
|
|
|
22
22
|
# :keep left as it is (already the bundle's, or kept)
|
|
23
23
|
# :skip left as it is, the bundle doesn't take it
|
|
24
24
|
# :move_to_trash moved to the trash (a removal)
|
|
25
|
+
# :remove deleted (an asset: save_first copies an edit to the
|
|
26
|
+
# trash first)
|
|
25
27
|
# :refuse the command stops
|
|
26
28
|
# :restore the bundle's text put back (SystemBundle at its version)
|
|
27
29
|
# save_first: a copy of the old bytes goes to the trash before a write
|
|
@@ -157,6 +159,55 @@ module Samagotchi
|
|
|
157
159
|
|
|
158
160
|
TABLES = { install: INSTALL, force: FORCE, upgrade: UPGRADE, remerge: REMERGE, restore: RESTORE,
|
|
159
161
|
uninstall: UNINSTALL }.freeze
|
|
162
|
+
|
|
163
|
+
# A bundle's own folder (rules, the plugin, scripts: AssetState), which
|
|
164
|
+
# every install replaces whole: the source's files go in, the rest go,
|
|
165
|
+
# an edit (AssetState#edited?) copied to the trash first. A cell is keyed
|
|
166
|
+
# [state, kind_of_file] or [state, :any]. A file the source has is
|
|
167
|
+
# written even when it has its bytes already (and so checked against the
|
|
168
|
+
# manifest's sha, as any other).
|
|
169
|
+
ASSET_WRITE = Action.new(do: :write_incoming, word: :installed, next_base: :on_disk)
|
|
170
|
+
ASSET_REMOVE = Action.new(do: :remove, word: nil, next_base: :none, record: :drop)
|
|
171
|
+
ASSET_GONE = Action.new(do: :skip, word: nil, next_base: :none, record: :drop)
|
|
172
|
+
|
|
173
|
+
# A plain install, install --force, and an upgrade with no record yet.
|
|
174
|
+
ASSET_INSTALL = {
|
|
175
|
+
%i[new any] => ASSET_WRITE, %i[unchanged any] => ASSET_WRITE.with(word: :up_to_date),
|
|
176
|
+
%i[pristine any] => ASSET_WRITE, %i[edited any] => ASSET_WRITE.with(save_first: true),
|
|
177
|
+
%i[linked any] => ASSET_WRITE,
|
|
178
|
+
# The source dropped it: it goes with the folder, without a word.
|
|
179
|
+
%i[dropped_pristine any] => ASSET_REMOVE, %i[dropped_edited any] => ASSET_REMOVE.with(save_first: true),
|
|
180
|
+
%i[missing any] => ASSET_GONE
|
|
181
|
+
}.freeze
|
|
182
|
+
|
|
183
|
+
# An upgrade (--force too) over the record: a file it replaces is
|
|
184
|
+
# updated, except a rule (an upgrade lists a replaced rule as installed:
|
|
185
|
+
# the corpus's rule-installed).
|
|
186
|
+
ASSET_UPGRADE = ASSET_INSTALL.merge(
|
|
187
|
+
%i[pristine any] => ASSET_WRITE.with(word: :updated),
|
|
188
|
+
%i[edited any] => ASSET_WRITE.with(word: :updated, save_first: true),
|
|
189
|
+
%i[linked any] => ASSET_WRITE.with(word: :updated),
|
|
190
|
+
%i[pristine rule] => ASSET_WRITE, %i[edited rule] => ASSET_WRITE.with(save_first: true),
|
|
191
|
+
%i[linked rule] => ASSET_WRITE
|
|
192
|
+
).freeze
|
|
193
|
+
|
|
194
|
+
# Uninstall: the folder goes (no source, so every file there is a
|
|
195
|
+
# dropped_* or a link), an edit copied to the trash first, --force or not.
|
|
196
|
+
ASSET_UNINSTALL = {
|
|
197
|
+
%i[dropped_pristine any] => ASSET_REMOVE, %i[dropped_edited any] => ASSET_REMOVE.with(save_first: true),
|
|
198
|
+
%i[linked any] => ASSET_REMOVE, %i[missing any] => ASSET_GONE
|
|
199
|
+
}.freeze
|
|
200
|
+
|
|
201
|
+
ASSET_TABLES = { install: ASSET_INSTALL, upgrade: ASSET_UPGRADE, uninstall: ASSET_UNINSTALL }.freeze
|
|
202
|
+
|
|
203
|
+
# The command's action for an AssetState: [state, kind_of_file], then
|
|
204
|
+
# [state, :any].
|
|
205
|
+
# @param command [Symbol] an ASSET_TABLES key
|
|
206
|
+
# @return [Action, nil] nil: the command never meets the state
|
|
207
|
+
def self.for_asset(command, asset)
|
|
208
|
+
table = ASSET_TABLES.fetch(command)
|
|
209
|
+
table[[asset.state, asset.kind_of_file]] || table[[asset.state, :any]]
|
|
210
|
+
end
|
|
160
211
|
# The kinds whose row depends on another bundle listing the file.
|
|
161
212
|
SHARED_ASKED = %i[dropped_pristine dropped_edited pristine pristine_no_base edited edited_no_base].freeze
|
|
162
213
|
|
|
@@ -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
|
|
@@ -34,8 +34,9 @@ module Samagotchi
|
|
|
34
34
|
class Installer
|
|
35
35
|
class InstallError < StandardError; end
|
|
36
36
|
|
|
37
|
-
# What the run did to one file (a memory, or a
|
|
38
|
-
#
|
|
37
|
+
# What the run did to one file (a memory, or a rule, plugin or script:
|
|
38
|
+
# AssetInstaller writes them too, and removes an older chi's hooks), as
|
|
39
|
+
# #results holds it. word is
|
|
39
40
|
# the report bucket (#summary): a real run's (:installed, :updated,
|
|
40
41
|
# :merged, :kept, :kept_pruned, :kept_shared, :kept_own (a file left as
|
|
41
42
|
# the user's), :up_to_date, :conflict, :removed) or a dry run's
|
|
@@ -166,8 +167,10 @@ module Samagotchi
|
|
|
166
167
|
would_install = with_word(:would_install)
|
|
167
168
|
would_update = with_word(:would_update)
|
|
168
169
|
fast_forward = with_word(:fast_forward)
|
|
169
|
-
|
|
170
|
-
|
|
170
|
+
# An older chi's class-file hooks this run removes: a line of their
|
|
171
|
+
# own, with why (AssetInstaller#retire_hooks).
|
|
172
|
+
retired, removed = with_word(:removed).partition { |key| @results[key].note == AssetInstaller::RETIRED }
|
|
173
|
+
would_retire, would_remove = with_word(:would_remove).partition { |key| @results[key].note == AssetInstaller::RETIRED }
|
|
171
174
|
|
|
172
175
|
lines << "Installed: #{installed.join(", ")}" unless installed.empty?
|
|
173
176
|
lines << "Updated: #{updated.join(", ")}" unless updated.empty?
|
|
@@ -178,6 +181,8 @@ module Samagotchi
|
|
|
178
181
|
lines << FileStanding.merged_line(would_merge, @manifest&.version, dry_run: true) unless would_merge.empty?
|
|
179
182
|
lines << "Removed (no longer in the bundle): #{removed.join(", ")}#{" (moved to #{trash_dir})" if trash_dir}" unless removed.empty?
|
|
180
183
|
lines << "Would remove (no longer in the bundle): #{would_remove.join(", ")}" unless would_remove.empty?
|
|
184
|
+
lines << "Removed (#{AssetInstaller::RETIRED}): #{retired.join(", ")}" unless retired.empty?
|
|
185
|
+
lines << "Would remove (#{AssetInstaller::RETIRED}): #{would_retire.join(", ")}" unless would_retire.empty?
|
|
181
186
|
lines << "Kept (local edits preserved): #{kept.join(", ")}" unless kept.empty?
|
|
182
187
|
lines << "Up to date: #{up_to_date.join(", ")}" unless up_to_date.empty?
|
|
183
188
|
lines << "Skipped: #{skipped.join(", ")}" unless skipped.empty?
|
|
@@ -213,11 +218,11 @@ module Samagotchi
|
|
|
213
218
|
def with_word(*words) = @results.select { |_, result| words.include?(result.word) }.keys
|
|
214
219
|
|
|
215
220
|
# A plain install over a bundle that is already installed from the same
|
|
216
|
-
# source: it rewrote the bundle's
|
|
221
|
+
# source: it rewrote the bundle's rules, plugin, scripts and its
|
|
217
222
|
# record, and left a memory the user wrote as it was. So the line says
|
|
218
223
|
# what changed, and points at the upgrade for the memories it kept.
|
|
219
224
|
def reinstall_line
|
|
220
|
-
line = "#{@name} was already installed from #{@source}: its
|
|
225
|
+
line = "#{@name} was already installed from #{@source}: its rules, plugin and scripts " \
|
|
221
226
|
"now match it and its record was rewritten"
|
|
222
227
|
# :kept_own is the only way a memory is left out of a plain install
|
|
223
228
|
# (its KEPT_OWN or HELD row): its bytes differ from the bundle's.
|
|
@@ -256,10 +261,10 @@ module Samagotchi
|
|
|
256
261
|
# #ref; a path resolved); one it can't place (an older record's
|
|
257
262
|
# relative path) never refuses.
|
|
258
263
|
# - a name chi ships comes from a shipped dir only, unless --force
|
|
259
|
-
# (install and upgrade): the plugin and
|
|
264
|
+
# (install and upgrade): the plugin and rules it replaces are chi's;
|
|
260
265
|
# - a plain install over a bundle from another source is refused,
|
|
261
266
|
# --force too (that overwrites files, it doesn't switch sources):
|
|
262
|
-
# it would delete the plugin and
|
|
267
|
+
# it would delete the plugin and rules and rewrite the record;
|
|
263
268
|
# - an upgrade may switch sources (a URL and a local copy of it are
|
|
264
269
|
# one bundle, an archive's file name has its version), and
|
|
265
270
|
# #source_change says so (the summary's first line).
|
|
@@ -351,19 +356,32 @@ module Samagotchi
|
|
|
351
356
|
@bundle_dir = @source_dir = prepared.dir
|
|
352
357
|
@source_owned = prepared.owned
|
|
353
358
|
@source_commit = prepared.commit
|
|
359
|
+
refuse_hooks
|
|
354
360
|
@manifest = Manifest.read(dir: @bundle_dir)
|
|
355
361
|
clash = @manifest.session_settings_clash
|
|
356
362
|
raise InstallError, clash if clash
|
|
357
363
|
rescue SourceNormalizer::UnknownSourceError => e
|
|
358
364
|
raise InstallError, "Source normalization failed: #{e.message}"
|
|
365
|
+
rescue Manifest::HooksRetired => e
|
|
366
|
+
# A rule, not a strictness option: refused strict or not, a dry run too.
|
|
367
|
+
raise InstallError, e.message
|
|
359
368
|
rescue Manifest::ValidationError => e
|
|
360
369
|
raise InstallError, e.message if @strict
|
|
361
370
|
|
|
362
371
|
@warnings << "No manifest found — proceeding without strict manifest validation"
|
|
363
372
|
end
|
|
364
373
|
|
|
374
|
+
# A source that ships class-file hooks (hooks/*.rb, listed or not) is
|
|
375
|
+
# refused before its manifest is read; its manifest's hooks: is
|
|
376
|
+
# Manifest.read's refusal (Manifest::HooksRetired either way).
|
|
377
|
+
# @raise [Manifest::HooksRetired]
|
|
378
|
+
def refuse_hooks
|
|
379
|
+
files = Manifest::HooksRetired.files_in(@bundle_dir)
|
|
380
|
+
raise Manifest::HooksRetired.for_files(files) unless files.empty?
|
|
381
|
+
end
|
|
382
|
+
|
|
365
383
|
# A bundle whose requires_chi this chi doesn't meet is not installed at
|
|
366
|
-
# all: --force is what installs it, and its plugin
|
|
384
|
+
# all: --force is what installs it, and its plugin and rules then
|
|
367
385
|
# warn that they won't load (AssetInstaller). The words come from
|
|
368
386
|
# Manifest.requires_chi_failure, so the requirement is said one way.
|
|
369
387
|
# A dry run is refused the same way: nothing there would load either.
|
|
@@ -555,8 +573,11 @@ module Samagotchi
|
|
|
555
573
|
# differs from its manifest's sha256. One kept or skipped isn't checked
|
|
556
574
|
# (it takes nothing from the bundle). What is checked is the source
|
|
557
575
|
# file, the bytes the write or merge takes, so a dry run checks what it
|
|
558
|
-
# would bring in, never the user's bytes on disk.
|
|
576
|
+
# would bring in, never the user's bytes on disk. A dev install follows
|
|
577
|
+
# its own tree; chi bundle check flags its stale shas instead.
|
|
559
578
|
def verify_memory_checksums
|
|
579
|
+
return if @dev
|
|
580
|
+
|
|
560
581
|
@bundle_md.each do |file_key|
|
|
561
582
|
result = @results[file_key]
|
|
562
583
|
next unless result&.action&.takes_source?
|
|
@@ -579,7 +600,7 @@ module Samagotchi
|
|
|
579
600
|
|
|
580
601
|
# The install's record (Provenance#write), only with a manifest and
|
|
581
602
|
# not on a dry run. An upgrade with conflicts writes it too: the new
|
|
582
|
-
# version, the plugin's
|
|
603
|
+
# version, the plugin's new sha, and the old base for each
|
|
583
604
|
# conflicted file, marked conflict (chi bundle status shows it).
|
|
584
605
|
# Skipping it left the replaced plugin failing its sha check.
|
|
585
606
|
# @param installed [AssetInstaller::Installed]
|
|
@@ -589,11 +610,8 @@ module Samagotchi
|
|
|
589
610
|
scope: @target_scope,
|
|
590
611
|
version: @manifest.version,
|
|
591
612
|
source_path: @source,
|
|
592
|
-
# AssetInstaller added the discovered hooks/*.rb to manifest.hooks.
|
|
593
|
-
hooks: @manifest.hooks,
|
|
594
613
|
trust_level: @manifest.trust_level,
|
|
595
614
|
source_commit: @source_commit,
|
|
596
|
-
hooks_files: installed.hook_files,
|
|
597
615
|
guardrails_files: installed.guardrail_files,
|
|
598
616
|
plugin_file: installed.plugin_file,
|
|
599
617
|
plugin_required: @manifest.plugin&.dig(:required) == true,
|