Skip to content
Open
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
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ organizing principle:

| Package | Role |
| ------- | ---- |
| [`@mieweb/cloud-types`](packages/cloud-types) | The portable contracts (`CloudDatabase`, `CloudBucket`, `CloudKV`, `CloudQueue`, `CloudStatefulNamespace`, `CloudVectorIndex`, `CloudAI`) plus `UnsupportedBindingError`. On Cloudflare these are exact aliases of the native binding types. |
| [`@mieweb/cloud-types`](packages/cloud-types) | The portable contracts (`CloudDatabase`, `CloudBucket`, `CloudKV`, `CloudQueue`, `CloudStatefulNamespace`, `CloudVectorIndex`, `CloudAI`, `CloudContainerNamespace`) plus `UnsupportedBindingError`. On Cloudflare these are exact aliases of the native binding types. |
| [`@mieweb/cloud-workers`](packages/cloud-workers) | The `DurableObject` base. Backs the **`mieweb:workers`** virtual import: re-exports `cloudflare:workers` on Cloudflare (workerd export condition), pure-JS base everywhere else. |
| [`@mieweb/cloud`](packages/cloud) | Umbrella entry — re-exports the contracts + `DurableObject` from one stable import surface. |
| [`@mieweb/cloud-local`](packages/cloud-local) | Local/Node **adapters**: D1→SQLite, R2→filesystem, KV→in-memory, Queues→in-process, Durable Objects→in-process registry. Vectorize/Workers AI surface explicit `UnsupportedBindingError`. Includes a Node host harness + migration runner. |
Expand Down Expand Up @@ -98,6 +98,24 @@ failing (e.g. Workers AI needs a model backend; Cloudflare local dev can't reach
Vectorize/AI without credentials), so one suite stays green everywhere. The
`local` target also runs as a plain `node --test` under `pnpm -r test`.

## Containers

Cloudflare **Containers** (DO-controlled Linux containers) are a reserved
surface of the contract: `CloudContainerNamespace` / `CloudContainerStub` in
`@mieweb/cloud-types`. App code uses the stock Cloudflare shape — a
`class MyContainer extends Container` (from `@cloudflare/containers`) paired
with a `containers` entry + DO binding in `wrangler.jsonc` — and stays
target-agnostic.

| Target | Support |
| ------ | ------- |
| `cloudflare` | ✅ native (`wrangler dev` / `wrangler deploy`, `wrangler containers ssh` for a shell) |
| `local` | ⏳ planned — Docker-backed adapter; today a container binding throws `UnsupportedBindingError` on use |
| `mieweb` | ⏳ planned — image distribution via skopeo → Harbor, cluster runtime TBD |

See [container-plan.md](container-plan.md) for the milestones, the skopeo/Harbor
image pipeline, and the `myapp` walkthrough.

## Status

Proof-of-concept. Consumed today as a git submodule; npm / Deno / Bun
Expand Down
332 changes: 332 additions & 0 deletions container-plan.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ implement the same Cloudflare-shaped contract.

| Package | Role |
| ------- | ---- |
| [`cloud-types`](cloud-types) | The portable contracts (`CloudDatabase`, `CloudBucket`, `CloudKV`, `CloudQueue`, `CloudStatefulNamespace`, `CloudVectorIndex`, `CloudAI`) plus `UnsupportedBindingError`. On Cloudflare these are exact aliases of the native binding types, so `worker/env.ts` keeps compiling unchanged. |
| [`cloud-types`](cloud-types) | The portable contracts (`CloudDatabase`, `CloudBucket`, `CloudKV`, `CloudQueue`, `CloudStatefulNamespace`, `CloudVectorIndex`, `CloudAI`, `CloudContainerNamespace`) plus `UnsupportedBindingError`. On Cloudflare these are exact aliases of the native binding types, so `worker/env.ts` keeps compiling unchanged. |
| [`cloud-workers`](cloud-workers) | The `DurableObject` base. Backs the **`mieweb:workers`** virtual import: re-exports `cloudflare:workers` on Cloudflare (workerd export condition), pure-JS base everywhere else. |
| [`cloud`](cloud) | Umbrella entry. Re-exports the contracts + `DurableObject` from one stable import surface (`@mieweb/cloud`). |
| [`cloud-local`](cloud-local) | Local/Node **adapters** (the POC): D1→SQLite, R2→filesystem, KV→in-memory, Queues→in-process, Durable Objects→in-process registry. Vectorize/Workers AI surface explicit `UnsupportedBindingError`. Includes the Node **host harness** that runs the unchanged worker handler and a migration runner. |
Expand Down
68 changes: 68 additions & 0 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# `@mieweb/cli` — the `mieweb` command

Target-aware wrapper over `wrangler`. On the `cloudflare` target (default)
every command is forwarded verbatim to `wrangler`; on `local`/`mieweb` the CLI
runs your unchanged worker on the Node host harness backed by the matching
adapters. See the [root README](../../README.md) for the full model.

```sh
mieweb [--target <cloudflare|local|mieweb>] <command> [...args]
```

## Images (Cloudflare Containers)

Build container images once, distribute them with **skopeo**
(see [container-plan.md](../../container-plan.md)).

**Prerequisites:** `skopeo` plus a builder — `buildah` (preferred) or `docker`.

```sh
brew install skopeo buildah # macOS
# apt/dnf install skopeo buildah # Linux
```

The CLI reads the `containers` array in `wrangler.jsonc` (class → Dockerfile)
and the per-target `registry` block in `mieweb.jsonc`:

```jsonc
// mieweb.jsonc
{
"targets": {
"mieweb": {
"registry": {
"url": "harbor.os.mieweb.org",
"project": "cloud-apps",
"username": "robot$cloud-apps+ci",
"authFile": "~/.config/mieweb/harbor-auth.json"
}
}
}
}
```

### Commands

```sh
mieweb images build # build every containers[] image (buildah/docker)
mieweb images push --target mieweb # build + skopeo copy → registry, pin digests
mieweb images push # cloudflare target: delegates to `wrangler containers push`
mieweb images inspect CONVERTER # skopeo inspect by binding name or class name
mieweb images status # lockfile pins vs. what's live in the registry

mieweb registry login --target mieweb # skopeo login (writes authFile; password prompted, never argv)
mieweb registry logout --target mieweb
```

### Conventions

- **Naming:** `docker://<registry.url>/<project>/<class_name lowercased>:<git short SHA>`,
plus a `latest` moving tag. `project` defaults to the wrangler app `name`.
- **Lockfile:** pushes pin the manifest digest per class per target in
`.mieweb/images.lock.json` (commit it — it's what makes deploys reproducible).
`mieweb images status` reports drift between the pin and the registry's `latest`.
- **Auth:** prefer `authFile` (written by `mieweb registry login`) over inline
credentials; secrets are never passed on the command line or logged.
Harbor robot accounts (`robot$project+name`) are the expected CI identity.
- **Cloudflare:** the managed registry is wrangler's job — `images push` on the
`cloudflare` target hands off to `wrangler containers push` rather than
reimplementing its auth with skopeo.
14 changes: 14 additions & 0 deletions packages/cli/mieweb-config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@
"properties": {
"runtime": { "type": "string" },
"port": { "type": "number" },
"registry": {
"type": "object",
"description": "OCI registry the CLI pushes container images to for this target (skopeo copy destination). Harbor for the mieweb target; unused on cloudflare (wrangler's managed registry).",
"properties": {
"url": { "type": "string", "description": "Registry host, e.g. harbor.os.mieweb.org." },
"project": { "type": "string", "description": "Registry project/namespace images are pushed under." },
"username": { "type": "string", "description": "Registry user (e.g. a Harbor robot account: robot$project+name)." },
"authFile": { "type": "string", "description": "Path to a containers-auth.json written by `skopeo login` / `mieweb registry login`. Preferred over inline credentials." },
"insecureSkipTlsVerify": { "type": "boolean", "description": "Disable TLS verification (dev registries only)." }
},
"required": ["url"],
"additionalProperties": true
},
"bindings": {
"type": "object",
"additionalProperties": {
Expand All @@ -42,6 +55,7 @@
"s3",
"valkey",
"valkey-queue",
"docker",
"unsupported"
]
},
Expand Down
24 changes: 24 additions & 0 deletions packages/cli/src/config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { parseJsonc } from './jsonc.mjs';
* @property {Record<string, unknown>} wrangler parsed wrangler.jsonc
* @property {Record<string, unknown>} raw parsed mieweb.jsonc (or {} )
* @property {Record<string, any>} targetConfig adapter config for the active target
* @property {Array<Record<string, any>>} containers wrangler `containers` entries (Cloudflare Containers)
* @property {Record<string, Record<string, any>>} containerBindings DO binding name → its wrangler `containers` entry, for container-backed bindings
*/

/**
Expand Down Expand Up @@ -52,6 +54,26 @@ export function loadConfig(opts = {}) {

const targetConfig = (raw.targets && raw.targets[target]) || {};

// Cloudflare Containers: wrangler's `containers` array pairs a DO class with
// an image; a DO binding whose class appears there is a container binding.
// (Reserved surface — see container-plan.md. wrangler handles these natively
// on the cloudflare target; other targets throw UnsupportedBindingError on
// use until a runtime adapter exists.)
const containers = Array.isArray(wrangler.containers)
? /** @type {Array<Record<string, any>>} */ (wrangler.containers)
: [];
/** @type {Record<string, Record<string, any>>} */
const containerBindings = {};
const doBindings =
/** @type {{ bindings?: Array<{ name: string, class_name: string }> }} */ (
wrangler.durable_objects
)?.bindings ?? [];
for (const c of containers) {
for (const b of doBindings) {
if (b.class_name === c.class_name) containerBindings[b.name] = c;
}
}

return {
configPath: miewebPath ?? wranglerPath,
root,
Expand All @@ -60,6 +82,8 @@ export function loadConfig(opts = {}) {
wrangler,
raw,
targetConfig,
containers,
containerBindings,
};
}

Expand Down
Loading
Loading