Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 0 additions & 39 deletions .github/workflows/publish.yml

This file was deleted.

34 changes: 34 additions & 0 deletions AGENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Agent guide

Development principles for working in this repo. Documentation for users lives in [`docs/`](docs/).

## Testing

Tests are organized around three components. Each runs independently.

### Canister (`canister-core`)

- **Location**: [`crates/canister-core/src/tests.rs`](crates/canister-core/src/tests.rs)
- **Run**: `cargo test -p canister-core`

`canister-core` is the library crate behind `canister`. Its unit tests cover all canister behaviors using a mock system context — no live replica needed: asset CRUD, encoding selection, HTTP semantics, certification, permissions, stable state, and streaming.

**Add tests here when** you change anything inside `canister-core`: new canister endpoints, modified serving logic, certification changes, permission rules, or upgrade/downgrade behavior.

### Plugin (`sync-core`)

- **Location**: inline `#[cfg(test)]` modules in each [`crates/sync-core/src/`](crates/sync-core/src/)`*.rs` file
- **Run**: `cargo test -p sync-core`

`sync-core` is the library crate behind `sync-plugin`. It has no WASI dependency and compiles natively. Its unit tests cover all sync business logic: directory scanning, MIME detection and encoding, operation diffing, batch sequencing, canister API calls and pagination, and authorization.

**Add tests here when** you change any sync logic: how files are discovered, how encodings are chosen, how diffs are computed, how batch operations are sequenced, or how permissions are managed. Prefer this over E2E for new logic — tests are fast and require no infrastructure.

### End-to-end (`e2e`)

- **Location**: [`crates/e2e/`](crates/e2e/)
- **Run**: `cargo test -p e2e`

E2E tests verify that the canister and plugin work correctly together through the `icp` CLI against a live local replica. Covers the basic sync workflow: deploy, no-op re-sync, content update, deletion, and multi-directory sync.

**Add tests here when** you introduce a new top-level workflow or change how the plugin integrates with the CLI or canister in a way that unit tests cannot exercise — for example, a new deploy mode or wire-protocol changes. Keep this suite small; unit tests are preferred for logic coverage.
112 changes: 56 additions & 56 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
[workspace]
members = ["assets-sync", "canister", "e2e", "ic-certified-assets", "plugin"]
members = ["crates/*"]
resolver = "2"

[workspace.package]
version = "0.0.0"
authors = ["DFINITY Stiftung <sdk@dfinity.org>"]
edition = "2021"
repository = "https://github.com/dfinity/certified-assets"
license = "Apache-2.0"
publish = false

[workspace.dependencies]
anyhow = "1.0.56"
Expand Down
76 changes: 1 addition & 75 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,77 +1,3 @@
# Certified Assets

Two Wasm modules, each backed by a library crate:

```
┌────────────────────┐ ┌────────────────────┐
│ canister/ │ │ plugin/ │
│ deployed to ICP │ │ loaded by icp-cli │
└─────────┬──────────┘ └─────────┬──────────┘
│ wraps │ wraps
┌─────────▼──────────┐ ┌─────────▼──────────┐
│ ic-certified- │ │ assets-sync/ │
│ assets/ │ │ │
└────────────────────┘ └────────────────────┘
```

## [`canister/`](canister/)

The ICP assets canister — a deployable WebAssembly canister that serves certified static assets over HTTP. It wraps `ic-certified-assets` and exposes the canister interface.

### [`ic-certified-assets/`](ic-certified-assets/)

The core business logic library. Handles asset storage, certification (response verification), streaming, and access control. `canister` depends on this crate; it can also be embedded in other canisters.

## [`plugin/`](plugin/)

A thin `icp-cli` sync plugin. Delegates all sync logic to `assets-sync`.

### [`assets-sync/`](assets-sync/)

Platform-agnostic library implementing the asset sync logic: directory scanning, MIME detection, content encoding, canister diffing, and the `CanisterCall` trait that abstracts the transport layer.

## Redirects, rewrites, and custom error pages

The canister honours a Netlify-style `_redirects` file at the root of the project's input directory. (The sync plugin only accepts one source directory — see [`plugin/README.md`](plugin/README.md#scope) for why.) Each line is `<from> <to> <status>`, where `<status>` is one of `{200, 301, 302, 307, 308, 404, 410}`:

```text
/old-page /new-page 301 # 3xx redirect (Location header)
/external https://example.com/ 302
/about /about.html 200 # 200 rewrite (serve target's body)
/blog/* /blog/index.html 200 # subtree rewrite
/missing /404.html 404 # custom error page
```

The file is consumed by the plugin and lowered to certified canister-side rules — a real asset at the rule's `from` path always wins. Ahead of the user's `_redirects`, the plugin auto-synthesises Cloudflare's [`auto-trailing-slash`](https://developers.cloudflare.com/workers/static-assets/routing/advanced/html-handling/#automatic-trailing-slashes-default) rule set for every `.html` asset, so `/foo.html` is reachable as `/foo`, `/bar/index.html` is reachable as `/bar/`, and the non-canonical forms (`/foo/`, `/foo/index`, etc.) 307 to the canonical URL. User-declared rules apply to paths the html-handling defaults don't claim — e.g. a SPA-style `/* /404.html 404` catch-all only fires for paths with no matching HTML asset. See [`plugin/README.md`](plugin/README.md#redirects) for the full reference and migration notes.

## Per-path response headers

The canister also honours a Netlify-style `_headers` file at the root of the project's input directory. Each block is a non-indented `<pattern>` line followed by one or more indented `Header-Name: value` lines:

```text
/_astro/*
Cache-Control: public, max-age=31536000, immutable

/*
X-Frame-Options: DENY
X-Robots-Tag: noindex
```

`<pattern>` is an absolute path with optional `*` wildcards — `/about` is exact, `/_astro/*` is a subtree, `/*.md` matches any `.md` file at any depth. A single `*` matches any sequence including `/` and empty; `**` is not supported (redundant) and neither is `:placeholder`. All matching rules apply per the Cloudflare Pages / Netlify semantics — same-name values across rules concatenate with `, ` (RFC 7230), with `Set-Cookie` carved out (RFC 6265). `Content-Type` is recognised but routed to the asset's stored media type instead of the appended response headers — see below. See [`plugin/README.md`](plugin/README.md#headers) for the full reference and reject list.

### `Content-Type` overrides

The canister derives a `Content-Type` for every asset from its media type and certifies it as part of the response. To override what `mime_guess::from_path` picks (or to add a `charset` parameter), set `Content-Type:` inside any `_headers` block:

```text
/*.md
Content-Type: text/markdown; charset=utf-8

/*.did
Content-Type: text/plain; charset=utf-8

/llms.txt
Content-Type: text/plain; charset=utf-8
```

The plugin extracts `Content-Type` and feeds it into `CreateAssetArguments.content_type` rather than appending it as a response header — so the canister emits exactly one `Content-Type` per response, no duplicates. Other headers in the same block continue to flow through `headers` as usual. `Content-Type` is single-valued, so when multiple blocks match the same asset the first matching `Content-Type` wins (other matching rules still contribute their non-`Content-Type` headers as normal).
An ICP assets canister and `icp-cli` sync plugin for serving certified static assets.
30 changes: 0 additions & 30 deletions TEST.md

This file was deleted.

Loading
Loading