Module: Clacky::ExtensionPackager
- Defined in:
- lib/clacky/extension/packager.rb
Overview
Packs a local extension container into a distributable zip, and installs a
zip (local path or URL) into the installed layer.
These are the local halves of the publish/marketplace flow: pack produces
the artifact that later gets uploaded, install is what a download performs.
Both are pure filesystem operations with no network dependency beyond the
optional URL fetch in install.
Defined Under Namespace
Constant Summary collapse
- MANIFEST =
"ext.yml"- MAX_ZIP_SIZE =
80 * 1024 * 1024
- OPEN_TIMEOUT =
Network timeouts for the download step.
read_timeoutis an idle timeout (resets on every chunk received), so a stalled connection with no new bytes flowing for this long is treated as dead — not a "download still going, just slow" situation, which large files on a slow link would otherwise trigger. 15- READ_TIMEOUT =
seconds to establish the connection
60- SYSTEM_METADATA =
Platform metadata that leaks in from the developer's OS; never ship it.
[".DS_Store", "__MACOSX", "Thumbs.db", "desktop.ini"].freeze
- NEVER_PACK_DIRS =
VCS and IDE metadata that must never ship in a pack, even without a .gitignore.
[".git", ".svn", ".hg", ".idea", ".vscode"].freeze
Class Method Summary collapse
-
.install(source, installed_dir: Clacky::ExtensionLoader::INSTALLED_DIR, force: false, on_progress: nil) ⇒ Object
Install a packed container into the
installedlayer. -
.install_bytes(data, filename: nil, **opts) ⇒ Object
Install from raw zip bytes (e.g. a multipart upload).
-
.pack(ext_id, source_dir: Clacky::ExtensionLoader::LOCAL_DIR, out_dir: Dir.pwd) ⇒ Object
Zip up a single container from the local layer into
out_dir.
Class Method Details
.install(source, installed_dir: Clacky::ExtensionLoader::INSTALLED_DIR, force: false, on_progress: nil) ⇒ Object
Install a packed container into the installed layer. source is a
local zip path or an http(s) URL. Validates the archive (single top
container with an ext.yml, no path traversal), extracts it, then runs
verify on the resolved installed layer.
on_progress is an optional callable that receives a stage hash, e.g.:
{ stage: "downloading", progress: 45 }
{ stage: "extracting" }
{ stage: "verifying" }
Caller can use this for progress reporting without coupling to internals.
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/clacky/extension/packager.rb', line 76 def install(source, installed_dir: Clacky::ExtensionLoader::INSTALLED_DIR, force: false, on_progress: nil) Dir.mktmpdir("clacky-ext-install") do |tmp| on_progress&.call({ stage: "downloading", progress: 0 }) zip_path = local_zip_for(source, tmp, on_progress: on_progress) on_progress&.call({ stage: "extracting" }) extract_root = File.join(tmp, "unpacked") FileUtils.mkdir_p(extract_root) extract_zip(zip_path, extract_root) ext_id, container_src = locate_container(extract_root) target = File.join(installed_dir, ext_id) if Dir.exist?(target) && !force raise Error, "extension #{ext_id.inspect} already installed at #{target} (pass force: true to overwrite)" end FileUtils.mkdir_p(target) FileUtils.cp_r(Dir.glob("#{container_src}/*"), target) on_progress&.call({ stage: "verifying" }) Clacky::ExtensionLoader.invalidate_cache! units = verify_installed(ext_id, installed_dir) Result.new(ext_id: ext_id, path: target, units: units) end end |
.install_bytes(data, filename: nil, **opts) ⇒ Object
Install from raw zip bytes (e.g. a multipart upload). Validates the filename/size, writes the bytes to a temp file inside a mktmpdir, then delegates to install(). The temp file lives for the whole install (extract reads it) and is cleaned up on return.
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/clacky/extension/packager.rb', line 108 def install_bytes(data, filename: nil, **opts) unless data.is_a?(String) && !data.empty? raise Error, "empty upload" end unless filename.to_s.downcase.end_with?(".zip") raise Error, "please upload a .zip file" end if data.bytesize > MAX_ZIP_SIZE raise Error, "upload exceeds #{MAX_ZIP_SIZE / 1024 / 1024}MB limit" end Dir.mktmpdir("clacky-ext-import") do |dir| zip_path = File.join(dir, "upload.zip") File.binwrite(zip_path, data) install(zip_path, **opts) end end |
.pack(ext_id, source_dir: Clacky::ExtensionLoader::LOCAL_DIR, out_dir: Dir.pwd) ⇒ Object
Zip up a single container from the local layer into out_dir.
Runs verify first (blocking on errors) and refuses protected/encrypted
units — those are produced by the platform's encryption pipeline, not
hand-packed. Returns a Result with the produced zip path.
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/clacky/extension/packager.rb', line 44 def pack(ext_id, source_dir: Clacky::ExtensionLoader::LOCAL_DIR, out_dir: Dir.pwd) slug = ext_id.to_s.strip raise Error, "invalid extension id: #{ext_id.inspect}" if slug.empty? container_dir = File.join(source_dir, slug) unless File.directory?(container_dir) && File.file?(File.join(container_dir, MANIFEST)) raise Error, "no container found at #{container_dir} (expected #{MANIFEST})" end refuse_id_folder_mismatch!(slug, container_dir) verify_container!(slug, container_dir) refuse_protected!(slug, container_dir) FileUtils.mkdir_p(out_dir) zip_path = File.join(out_dir, "#{slug}.zip") File.delete(zip_path) if File.exist?(zip_path) write_zip(container_dir, zip_path) enforce_zip_size!(zip_path) Result.new(ext_id: slug, path: zip_path, units: nil) end |