class Manifest

Records the content and import mappings of a static package projection.

Definitions

def self.build(base:, imports:, packages:)

Build a deterministic manifest.

Signature

parameter base String

The public URL prefix for static packages.

parameter imports Hash(String, String)

The import-map entries.

parameter packages Hash(String, Hash)

The installed package metadata.

returns Manifest

The generated manifest.

Implementation

def self.build(base:, imports:, packages:)
	data = {
		"format" => 1,
		"base" => base,
		"imports" => imports.sort.to_h,
		"packages" => packages.sort.to_h,
	}
	
	data["digest"] = Digest::SHA256.hexdigest(JSON.generate(data))
	new(data)
end

def self.load(root)

Load a manifest from a static output directory.

Signature

parameter root String | Pathname

The static output directory.

returns Manifest

The loaded manifest.

raises CheckError

If the manifest is missing or malformed.

Implementation

def self.load(root)
	path = Pathname.new(root) + FILENAME
	new(JSON.parse(path.read))
rescue Errno::ENOENT
	raise CheckError, "Static package manifest does not exist at #{path}!"
rescue JSON::ParserError => error
	raise CheckError, "Could not parse #{path}: #{error.message}"
end

def initialize(data)

Initialize a manifest with its serialized data.

Signature

parameter data Hash

The manifest data.

Implementation

def initialize(data)
	@data = data
end

attr :data

Signature

attribute Hash

The serialized manifest data.

def write(root)

Write the manifest into a static output directory.

Signature

parameter root String | Pathname

The static output directory.

returns Integer

The number of bytes written.

Implementation

def write(root)
	path = Pathname.new(root) + FILENAME
	path.write(JSON.pretty_generate(@data) + "\n")
end

def import_map

Extract the browser import map.

Signature

returns Hash

An import-map object containing the configured imports.

Implementation

def import_map
	{"imports" => @data.fetch("imports", {})}
end

def valid_tree?(root)

Check whether every manifested file exists with the expected content.

Signature

parameter root String | Pathname

The static output directory.

returns Boolean

Whether the directory exactly matches the manifest.

Implementation

def valid_tree?(root)
	root = Pathname.new(root)
	expected = []
	
	@data.fetch("packages").each do |name, package|
		package.fetch("files").each do |relative_path, digest|
			path = root + name + relative_path
			expected << path.relative_path_from(root).to_s
			
			return false unless path.file?
			return false unless Digest::SHA256.file(path).hexdigest == digest
		end
	end
	
	actual = root.glob("**/*", File::FNM_DOTMATCH).select(&:file?).map do |path|
		path.relative_path_from(root).to_s
	end
	actual.delete(FILENAME)
	
	actual.sort == expected.sort
end