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
6 changes: 3 additions & 3 deletions .github/workflows/npm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ jobs:
version: 10.11.0
- uses: actions/setup-node@v4
with:
node-version: 22
node-version-file: .nvmrc
cache: pnpm
cache-dependency-path: crates/spock-runtime/studio/pnpm-lock.yaml

Expand Down Expand Up @@ -123,7 +123,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
node-version-file: .nvmrc
registry-url: "https://registry.npmjs.org"

- name: use latest npm (Trusted Publishing needs >= 11.5.1)
Expand Down Expand Up @@ -198,7 +198,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
node-version-file: .nvmrc

- name: install spock from npm
run: |
Expand Down
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24.18.0
1 change: 1 addition & 0 deletions Cargo.lock

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

34 changes: 27 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -637,19 +637,39 @@ UI-session state and experience behavior, with Spock as its canonical
provider. No fact may be authoritative in both languages.

Uhura is a subsystem of the Spock project: its canonical source lives in its
own repository and is included here as a git submodule at `uhura/`. Today this
is topology only — Spock's workspace, build, and CI do not touch the
submodule, and `cargo` builds work without it (clone with
`--recurse-submodules` only if you want the Uhura tree). Once Uhura is
minimally stable, its tooling ships through the unified `spock` toolchain,
and the runtime integration lands as contract projection plus a provider
adapter.
own repository and is included here as a git submodule at `uhura/`. Spock's
core workspace, default build, and CI remain independent of the submodule, so
`cargo` builds work without it; clone with `--recurse-submodules` for the
explicit composition runner below. Once Uhura is minimally stable, its tooling
ships through the unified `spock` toolchain, and the runtime integration lands
as contract projection plus a provider adapter.

From an umbrella checkout, the general composition runner accepts any Spock
program and Uhura project. For the Instagram example:

```sh
./scripts/spock-uhura.sh \
examples/instagram-poc/app.spock \
uhura/examples/instagram-uhura
```

It builds the two Rust launchers, starts the requested Spock authority on port
4000, waits for it to become ready, and opens the requested Uhura project
through its read-only Editor on <http://127.0.0.1:8787/>. The Editor's Play
button enters the live prototype at `/play` without starting another process.
`--spock-port` and `--uhura-port` override those defaults; the project's
provider configuration must address the same Spock port. `Ctrl-C` stops both
runtimes. Contributor frontend tooling in Spock and Uhura uses the same Node 24
LTS pin from their respective `.nvmrc` files and pnpm 10.11.0; Node is not
required by this runtime command.

## Repository Layout

- `examples/` contains product requirements and current-valid Spock examples.
- `docs/rfd/` contains discussion drafts and proposal-only language ideas.
- `npm/` contains package metadata for npm name reservation.
- `scripts/` contains umbrella composition tooling that keeps Spock and Uhura
independently buildable.
- `uhura/` is the Uhura client language (git submodule of
[gridaco/uhura](https://github.com/gridaco/uhura); not yet wired into the
build).
Expand Down
3 changes: 3 additions & 0 deletions crates/spock-runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ hex = "0.4"
getrandom = "0.2"
# content-type inference for `file("...")` seed assets (RFD 0018).
mime_guess = "2"
# Permissive CORS on the whole surface: v0 is the open dev tier bound to
# 127.0.0.1, and browser clients on another local origin must reach it.
tower-http = { version = "0.6", features = ["cors"] }
# Embeds the compiled studio SPA (studio/dist) into the binary. `debug-embed`
# forces embedding in debug builds too, so `spock run` serves it from any cwd
# (matching the old include_str! behavior) — the console stays fully offline.
Expand Down
12 changes: 11 additions & 1 deletion crates/spock-runtime/src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use rusqlite::types::{Value as SqlValue, ValueRef};
use rust_embed::RustEmbed;
use serde_json::{json, Map, Value as JsonValue};
use spock_lang::ir::{FnArity, Table, Type};
use tower_http::cors::CorsLayer;

use crate::error::ApiError;
use crate::filter;
Expand Down Expand Up @@ -110,7 +111,16 @@ pub fn router(app: Arc<App>) -> Result<Router, StartupError> {
);
}

Ok(base.fallback(not_found).with_state(app).merge(gql))
// Permissive CORS across the whole surface, preflight included: v0 is the
// open dev tier on 127.0.0.1 (RFD 0014 — the actor header is deliberately
// forgeable), so a browser client on another local origin (e.g. a Uhura
// shell) may call `/graphql/v1` and `/rest/v1/rpc/{fn}` with `content-type`
// and `x-spock-actor` headers. Unconditional by decision, like the reads.
Ok(base
.fallback(not_found)
.with_state(app)
.merge(gql)
.layer(CorsLayer::permissive()))
}

/// Serve the app on an already-bound listener until the task is stopped.
Expand Down
3 changes: 3 additions & 0 deletions crates/spock-runtime/studio/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
engine-strict=true
manage-package-manager-versions=true
package-manager-strict-version=true
10 changes: 7 additions & 3 deletions crates/spock-runtime/studio/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@ editing the console needs Node.

## Develop

Use the repository's Node 24 LTS `.nvmrc` and the pnpm version declared in
`package.json`:

```sh
pnpm install
nvm use
corepack pnpm install --frozen-lockfile
# in another terminal: spock run <program>.spock --port 4000
pnpm dev # Vite dev server on :5173, proxies /~contract, /rest,
corepack pnpm dev # Vite dev server on :5173, proxies /~contract, /rest,
# /graphql, /~personas, /~whoami to :4000 (HMR)
```

Expand All @@ -33,7 +37,7 @@ pnpm dev # Vite dev server on :5173, proxies /~contract, /rest,
After changing anything under `src/` or `index.html`:

```sh
pnpm build # tsc -b && vite build -> dist/
corepack pnpm build # tsc -b && vite build -> dist/
cargo build # re-embeds dist/ into the binary (run from the repo root)
```

Expand Down
7 changes: 6 additions & 1 deletion crates/spock-runtime/studio/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
"private": true,
"version": "0.0.0",
"type": "module",
"packageManager": "pnpm@10.11.0",
"engines": {
"node": "24.x",
"pnpm": "10.11.0"
},
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build && node -e \"require('fs').writeFileSync('dist/.gitkeep','')\"",
Expand All @@ -25,7 +30,7 @@
"tw-animate-css": "^1.4.0"
},
"devDependencies": {
"@types/node": "^24.13.2",
"@types/node": "24.13.3",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.3",
Expand Down
2 changes: 1 addition & 1 deletion crates/spock-runtime/studio/pnpm-lock.yaml

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

21 changes: 21 additions & 0 deletions crates/spock-runtime/tests/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,27 @@ async fn the_whole_protocol() {
assert_eq!(resp.status().as_u16(), 404); // table writes live on /graphql/v1
}

#[tokio::test]
async fn local_browser_clients_can_preflight_the_protocol() {
let base = start().await;
let response = reqwest::Client::new()
.request(reqwest::Method::OPTIONS, format!("{base}/graphql/v1"))
.header("Origin", "http://127.0.0.1:8787")
.header("Access-Control-Request-Method", "POST")
.header(
"Access-Control-Request-Headers",
"content-type,x-spock-actor",
)
.send()
.await
.expect("OPTIONS preflight");

assert_eq!(response.status(), reqwest::StatusCode::OK);
assert_eq!(response.headers()["access-control-allow-origin"], "*");
assert_eq!(response.headers()["access-control-allow-methods"], "*");
assert_eq!(response.headers()["access-control-allow-headers"], "*");
}

async fn rpc(base: &str, name: &str, body: Option<Value>) -> (u16, Value) {
let mut req = reqwest::Client::new().post(format!("{base}/rest/v1/rpc/{name}"));
if let Some(body) = body {
Expand Down
3 changes: 2 additions & 1 deletion docs/rfd/0020-distribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ this has a single, simple answer: **every build job runs `pnpm build` before
`cargo build`, guarded.** Per job:

1. `actions/checkout`
2. `pnpm/action-setup@v4`; `actions/setup-node@v4` with `node-version: 22`,
2. `pnpm/action-setup@v4`; `actions/setup-node@v4` with the repository's
Node 24 LTS `.nvmrc`,
`cache: pnpm`,
`cache-dependency-path: crates/spock-runtime/studio/pnpm-lock.yaml`
3. `pnpm -C crates/spock-runtime/studio install --frozen-lockfile`
Expand Down
Loading
Loading