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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ projects/
.local/

run-spaceapp-*.tgz
.dev-stack/
113 changes: 113 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,119 @@ select a short temporary directory you own and set `TMPDIR` for the command.
On Windows run this particular smoke check inside WSL or a Linux development
container. It does not certify the Windows/macOS installer.

## Run the full app from source

When you need the complete SpaceApp stack (PostgreSQL with `pgvector`, Temporal,
the API in watch mode, the Vite web dev server, the Temporal worker, and the
CLI host with the preconfigured OpenCode CLI) running from your checkout with
hot reload, start the contributor source stack after `npm ci`:

```sh
npm run dev:stack
```

Prerequisites:

- Node **>=22.12 <23** and npm (same as above)
- Docker Engine / Docker Desktop with `docker compose` available (Linux, macOS,
or Windows 11 with WSL2 integration enabled)

What `npm run dev:stack` does:

1. Reads the digest-pinned PostgreSQL (`pgvector`) and Temporal container images
from `packages/run-spaceapp/templates/compose.yml` and starts them under the
isolated `spaceapp-dev` Compose project bound only to `127.0.0.1`.
2. Generates per-checkout secrets and runtime state under `.dev-stack/`
(git-ignored, `0700`/`0600` permissions) and runs `npm run db:migrate`.
3. Installs the pinned `@opencode/cli` version from `Dockerfile` into
`.dev-stack/opencode` and writes local CLI wrappers into `.dev-stack/bin` so
the **Getting Started** room's OpenCode CLI pane reaches `RUNNING` without an
external AI provider account.
4. Builds workspace packages and supervises `packages/cli-host`, `apps/worker`,
`apps/api` (`tsx watch`), and `apps/web` (Vite HMR).
5. Prints the Web URL, API URL, and the one-time owner setup token (until owner
setup is claimed).

Default loopback ports (chosen so `dev:stack` never collides with an installed
`run-spaceapp` instance on `4910`/`4911`):

- Web (Vite dev server): `127.0.0.1:5911` (override with `SPACE_WEB_PORT` or `--web-port`)
- API: `127.0.0.1:5910` (override with `SPACE_API_PORT` or `--api-port`)
- PostgreSQL: `127.0.0.1:55432` (override with `SPACE_DEV_POSTGRES_PORT` or `--postgres-port`)
- Temporal: `127.0.0.1:57233` (override with `SPACE_DEV_TEMPORAL_PORT` or `--temporal-port`)

Stop the stack while keeping your database volume and claimed owner account:

```sh
npm run dev:stack -- down
```

Remove the `spaceapp-dev` containers, volumes, and `.dev-stack/` state directory
to return to a clean first-run state:

```sh
npm run dev:stack -- reset
```

Verify readiness, claim owner setup over HTTP, and verify that the Getting
Started OpenCode CLI pane reaches `RUNNING` without a browser TTY:

```sh
npm run dev:stack:smoke
```

Sample readiness and smoke output:

```text
============================================================
SpaceApp contributor dev stack is ready!
Web URL: http://127.0.0.1:5911
API URL: http://127.0.0.1:5910
Readiness: http://127.0.0.1:5910/readyz
Setup token: <one-time-owner-setup-token>
(One-time owner setup token — enter this at the Web URL to create the owner account)
Stop stack: npm run dev:stack -- down
Reset stack: npm run dev:stack -- reset
============================================================

{
"ok": true,
"readyzOk": true,
"roomName": "Getting Started",
"runtimeId": "cli:opencode",
"status": "RUNNING"
}
```

### Platform notes (Linux, macOS, Windows 11 WSL2)

- **Linux:** Ensure your user can reach the Docker daemon (`docker info`) and
`docker compose version` succeeds. All ports bind strictly to `127.0.0.1`.
- **macOS (Docker Desktop or Colima):** Start Docker Desktop or Colima
(`colima start --cpu 4 --memory 8`) before running `npm run dev:stack`. Keep
the repository inside your macOS user home directory so bind-mounted secret
files under `.dev-stack/secrets/` are shared into the Linux VM automatically.
If `node-pty` needs to compile from source, install Xcode Command Line Tools
(`xcode-select --install`).
- **Windows 11 (WSL2):** Run `npm run dev:stack` inside a WSL2 Linux
distribution (for example Ubuntu 24.04) with Docker Desktop WSL2 integration
enabled, and keep the checkout on the Linux ext4 filesystem inside WSL2 (not
`/mnt/c/`) for fast file watching and Unix domain socket support.

### Troubleshooting

- **Port already in use:** Override any colliding loopback port with
`SPACE_API_PORT`, `SPACE_WEB_PORT`, `SPACE_DEV_POSTGRES_PORT`, or
`SPACE_DEV_TEMPORAL_PORT` (or `--api-port`, `--web-port`, `--postgres-port`,
`--temporal-port`).
- **Docker daemon or container health check failure:** Verify `docker ps` works
and run `npm run dev:stack -- reset` to recreate the `spaceapp-dev` containers
and volumes cleanly.
- **Socket path length limit:** The CLI host Unix socket lives at
`.dev-stack/run/pane-host.sock`. If your checkout path is deeply nested
(>90 characters), pass `--state-dir /tmp/spaceapp-dev-stack` so the socket
path stays within the OS `sun_path` limit.

## Make and verify a small change

Work on a branch, not on `main`, so the change can become a pull request:
Expand Down
22 changes: 11 additions & 11 deletions PUBLIC_EXPORT_MANIFEST.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
"schemaVersion": 2,
"sourceCommit": "7619ef952387dba2480afecdb1ae332a0dc489ff",
"fileCount": 4729,
"transformations": 393,
"transformations": 395,
"files": [
{
"path": ".gitignore",
"sourceObjectId": "1fdf2ac77a391fc36521502732002c96a11913d4",
"sha256": "18242d3b694d31c2e42ccf6b02261a946906352537ab4b83a5f3278b09584aea",
"size": 1144
"sha256": "1d53a86657539079e992eaf1bc0bfd368d53e407e02066eb1c507b10d6553ed1",
"size": 1156
},
{
"path": ".nvmrc",
Expand All @@ -20,8 +20,8 @@
{
"path": "CONTRIBUTING.md",
"sourceObjectId": "078772388754304e33d238fefaad2f04e9120107",
"sha256": "14f559c40ed46089c3fb74339d1b225cdd817fc61ef5a39bf1dce8ea28a83216",
"size": 5143
"sha256": "c19a86615553367d0dfb6a3f0cfba59c30b9923cda2909b47681e6f078d39faf",
"size": 9839
},
{
"path": "LICENSE",
Expand Down Expand Up @@ -21512,8 +21512,8 @@
{
"path": "apps/web/tests/vite-config.test.ts",
"sourceObjectId": "f33db638236a1673b595039d8c602a983d09d012",
"sha256": "9f39d7a43d245ebafbc6720d6a99a94461a82890fb5423c7d2ae520830df1d7d",
"size": 1259
"sha256": "0ea0f47866f63699de3d73a0a346c0455b0a0ca16b98f21ce72f731187f374af",
"size": 1701
},
{
"path": "apps/web/tests/vnc-pane.test.tsx",
Expand Down Expand Up @@ -21662,8 +21662,8 @@
{
"path": "apps/web/vite.config.ts",
"sourceObjectId": "440a79e31a3f86df0c66bb67ed1d44a4dbcba128",
"sha256": "6903aa14b349193f17c903d4c14f984dbb547868a650ef3acc7a7f12b9d8849c",
"size": 2700
"sha256": "369aaeebf0150947abd4abb98f0f6fbaee91b2622fc469e138d09542ef2ea71b",
"size": 3506
},
{
"path": "apps/web/vite.demo-boundary.ts",
Expand Down Expand Up @@ -22520,8 +22520,8 @@
{
"path": "package.json",
"sourceObjectId": "62128b7e59e9f56b96f4d6685e4a50155f02f4c5",
"sha256": "211f8b2305dd627f738e6b1f1246cea555165cdb1d657669c220c271f6ed5ef4",
"size": 4220
"sha256": "f7de908634509f6e54b81b5d895c478c4496fa9f12dcc79acb2693316f907734",
"size": 4300
},
{
"path": "packages/browser-host/package.json",
Expand Down
8 changes: 7 additions & 1 deletion apps/web/tests/vite-config.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @vitest-environment node

import { describe, expect, it } from "vitest";
import { manualChunkForModule } from "../vite.config.js";
import { manualChunkForModule, resolveViteApiOrigin } from "../vite.config.js";

describe("web bundle chunk policy", () => {
it("isolates React and xterm while leaving feature modules available for lazy chunks", () => {
Expand All @@ -15,4 +15,10 @@ describe("web bundle chunk policy", () => {
expect(manualChunkForModule("/workspace/node_modules/graphology/dist/graphology.js")).toBeUndefined();
expect(manualChunkForModule("/workspace/src/features/browser-pane/BrowserPane.tsx")).toBeUndefined();
});

it("resolves the Vite proxy target from SPACE_API_ORIGIN or SPACE_API_PORT while keeping 4910 as default", () => {
expect(resolveViteApiOrigin({})).toBe("http://127.0.0.1:4910");
expect(resolveViteApiOrigin({ SPACE_API_PORT: "5910" })).toBe("http://127.0.0.1:5910");
expect(resolveViteApiOrigin({ SPACE_API_ORIGIN: "http://127.0.0.1:5999/", SPACE_API_PORT: "5910" })).toBe("http://127.0.0.1:5999");
});
});
36 changes: 31 additions & 5 deletions apps/web/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import { retainOpenTabAssets } from "./vite.asset-retention.js";
import react from "@vitejs/plugin-react";
import { demoBundleBoundaryPlugin } from "./vite.demo-boundary.js";

/**
* Maps a Rollup module identifier to its manual vendor chunk name when applicable.
*
* @param moduleId - Absolute or virtual module identifier passed by Rollup.
* @returns The vendor chunk name, or `undefined` to let Rollup decide.
*/
export function manualChunkForModule(moduleId: string): string | undefined {
const normalizedId = moduleId.replaceAll("\\", "/");
if (
Expand All @@ -17,6 +23,21 @@ export function manualChunkForModule(moduleId: string): string | undefined {
return undefined;
}

/**
* Resolves the upstream API origin used by the Vite dev server proxy.
*
* @param env - Process environment variables to inspect.
* @returns Normalized HTTP origin for the SpaceApp API server.
*/
export function resolveViteApiOrigin(env: NodeJS.ProcessEnv = process.env): string {
const explicit = env.SPACE_API_ORIGIN?.trim();
if (explicit) return explicit.replace(/\/+$/, "");
const apiPort = env.SPACE_API_PORT?.trim() || "4910";
return `http://127.0.0.1:${apiPort}`;
}

const apiOrigin = resolveViteApiOrigin();

export default defineConfig({
plugins: [react(), demoBundleBoundaryPlugin(), retainOpenTabAssets()],
// The icon package ships as source; the alias keeps worktrees (which share live node_modules) resolving this checkout.
Expand All @@ -34,11 +55,16 @@ export default defineConfig({
host: process.env.SPACE_WEB_HOST ?? "0.0.0.0",
port: Number.parseInt(process.env.SPACE_WEB_PORT ?? "4911", 10),
proxy: {
"/api": "http://127.0.0.1:4910",
"/plugins": "http://127.0.0.1:4910",
"/healthz": "http://127.0.0.1:4910",
"/readyz": "http://127.0.0.1:4910",
"/version": "http://127.0.0.1:4910"
"/api": {
target: apiOrigin,
ws: true
},
"/plugins": apiOrigin,
"/logout": apiOrigin,
"/healthz": apiOrigin,
"/livez": apiOrigin,
"/readyz": apiOrigin,
"/version": apiOrigin
}
},
test: {
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"dev:api": "npm run dev -w @space/api",
"dev:web": "npm run dev -w @space/web",
"dev:worker": "npm run dev -w @space/worker",
"dev:stack": "node scripts/dev-stack.mjs",
"db:migrate": "npm run migrate -w @space/db",
"build": "npm run build -w @space/contracts && npm run build -w @space/icons && npm run build -w @space/streaming && npm run build -w @space/opencode-control && npm run build -w @space/memory-graph && npm run build -w @space/browser-host && npm run build -w @space/cli-host && npm run build -w @space/codex-app-server && npm run build -w @space/runtime && npm run build -w @space/db && npm run build -w @space/gateway && node scripts/build-demo-projects.mjs && npm run build -w @space/api && npm run build -w @space/worker && npm run build -w @space/web && node scripts/fix-build-permissions.mjs",
"public:export:verify": "node scripts/public-export.mjs --verify",
Expand All @@ -58,7 +59,7 @@
"build:demos": "node scripts/build-demo-projects.mjs",
"test:architecture": "node --test scripts/tests/domain-boundaries.test.mjs scripts/tests/composition-root-size.test.mjs scripts/tests/quality-known-limitations.test.mjs && node scripts/domain-boundaries.mjs",
"smoke:offline": "npm run build -w @space/cli-host && node scripts/contributor-smoke.mjs",
"test:contributor": "node --test scripts/tests/contributor-smoke.test.mjs",
"test:contributor": "node --test scripts/tests/contributor-smoke.test.mjs scripts/tests/dev-stack.test.mjs",
"lint": "eslint --max-warnings=-1 apps packages scripts"
},
"devDependencies": {
Expand Down
Loading