Skip to content

Commit d2162fd

Browse files
Askirclaude
andcommitted
feat(dev-ui): serve dev UI under /dev/ path, proxy user app at root
Move the dev UI to /dev/* so the user's Next.js app can be served at / via a reverse proxy to localhost:3000. This enables cloud-dev machines to expose both the dev UI and the running app on the same domain. - Add Vite base path /dev/ and update all frontend fetch URLs - Restructure dev-server routing: /dev/* → dev UI, /* → user app proxy - Move WebSocket endpoint to /dev/ws - Add proxy.ts for forwarding requests to user app - Fix entrypoint.sh: blocking node_modules copy + runcrayon symlink pin - Fix postinstall to preserve runcrayon symlinks in container - Update auth-server health check to ping /dev/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 406899a commit d2162fd

File tree

19 files changed

+187
-89
lines changed

19 files changed

+187
-89
lines changed

CLAUDE.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,33 @@ my-app/
138138
- **Lint/Format:** Biome — `pnpm biome check`
139139
- **CI:** GitHub Actions (`publish-dev.yml`) publishes to npm with `dev` tag on push to main
140140

141+
### Testing Local Changes Against Cloud
142+
143+
To test local core changes on a cloud dev machine:
144+
145+
1. **Build & push a Docker image with your changes:**
146+
```bash
147+
cd packages/core/docker && ./build-dev.sh <tag>
148+
```
149+
This builds the core package, packs it, builds/pushes the Docker image to `registry.fly.io/crayon-cloud-dev-image:<tag>`, and updates `CLOUD_DEV_IMAGE` in `packages/auth-server/.env.local`.
150+
151+
2. **Start the local auth server** (separate terminal):
152+
```bash
153+
cd packages/auth-server && pnpm dev
154+
```
155+
156+
3. **Create a new cloud machine using the local auth server:**
157+
```bash
158+
CRAYON_SERVER_URL=http://localhost:3000 pnpm --filter runcrayon exec node dist/cli/index.js cloud run
159+
```
160+
161+
4. **Open the dev UI** at `https://<fly-app-name>.fly.dev/dev/`
162+
163+
To update an existing cloud machine to a new image:
164+
```bash
165+
cd packages/core/docker && ./update-machines.sh <fly-app-name> [image]
166+
```
167+
141168
## Deployment
142169

143170
- **auth-server:** `cd packages/auth-server && flyctl deploy`

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,27 @@ crayon/
7979
└── uptime-app/ # Example application
8080
```
8181

82+
## Testing Local Changes Against Cloud
83+
84+
To test local core changes on a cloud dev machine:
85+
86+
1. **Build & push a Docker image with your changes:**
87+
```bash
88+
cd packages/core/docker && ./build-dev.sh <tag>
89+
```
90+
91+
2. **Start the local auth server** (separate terminal):
92+
```bash
93+
cd packages/auth-server && pnpm dev
94+
```
95+
96+
3. **Create a new cloud machine using the local auth server:**
97+
```bash
98+
CRAYON_SERVER_URL=http://localhost:3000 pnpm --filter runcrayon exec node dist/cli/index.js cloud run
99+
```
100+
101+
4. **Open the dev UI** at `https://<fly-app-name>.fly.dev/dev/`
102+
82103
## Requirements
83104

84105
- Node.js 20+

packages/auth-server/src/app/api/cloud-dev/status/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export async function GET(req: NextRequest) {
6363
if (state === "started" || state === "running") {
6464
// Ping the app URL to verify it responds
6565
try {
66-
const resp = await fetch(appUrl!, {
66+
const resp = await fetch(`${appUrl!}/dev/`, {
6767
signal: AbortSignal.timeout(5000),
6868
});
6969
if (resp.ok || resp.status < 500) {

packages/core/dev-ui/src/components/BottomPanel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export function BottomPanel({ tabs, defaultTab, onClose }: BottomPanelProps) {
5656

5757
// Fetch claude command hint
5858
useEffect(() => {
59-
fetch("/api/claude-command")
59+
fetch("/dev/api/claude-command")
6060
.then((res) => res.ok ? res.json() : null)
6161
.then((data) => {
6262
if (data?.isCloud) {

packages/core/dev-ui/src/components/IntegrationSection.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export function IntegrationSection({
4848
const handleConnect = useCallback(async () => {
4949
setConnecting(true);
5050
try {
51-
const res = await fetch("/api/nango/connect-session", {
51+
const res = await fetch("/dev/api/nango/connect-session", {
5252
method: "POST",
5353
headers: { "Content-Type": "application/json" },
5454
body: JSON.stringify({ integration_id: integrationId }),

packages/core/dev-ui/src/components/RunWorkflowModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export function RunWorkflowModal({ dag, onClose, onSuccess }: RunWorkflowModalPr
6565

6666
setRunning(true);
6767
try {
68-
const res = await fetch(`/api/workflows/${encodeURIComponent(dag.workflowName)}/run`, {
68+
const res = await fetch(`/dev/api/workflows/${encodeURIComponent(dag.workflowName)}/run`, {
6969
method: "POST",
7070
headers: { "Content-Type": "application/json" },
7171
body: JSON.stringify({ input }),

packages/core/dev-ui/src/components/TestSection.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export function TestSection({ dag, onSuccess }: TestSectionProps) {
4646

4747
setRunning(true);
4848
try {
49-
const res = await fetch(`/api/workflows/${encodeURIComponent(dag.workflowName)}/run`, {
49+
const res = await fetch(`/dev/api/workflows/${encodeURIComponent(dag.workflowName)}/run`, {
5050
method: "POST",
5151
headers: { "Content-Type": "application/json" },
5252
body: JSON.stringify({ input }),

packages/core/dev-ui/src/hooks/useConnections.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export function useConnections() {
2323

2424
const fetchConnections = useCallback(async () => {
2525
try {
26-
const res = await fetch("/api/connections");
26+
const res = await fetch("/dev/api/connections");
2727
if (res.ok) {
2828
setConnections(await res.json());
2929
}
@@ -40,7 +40,7 @@ export function useConnections() {
4040

4141
const upsert = useCallback(
4242
async (mapping: Omit<ConnectionMapping, "updated_at">) => {
43-
await fetch("/api/connections", {
43+
await fetch("/dev/api/connections", {
4444
method: "PUT",
4545
headers: { "Content-Type": "application/json" },
4646
body: JSON.stringify(mapping),
@@ -53,7 +53,7 @@ export function useConnections() {
5353

5454
const remove = useCallback(
5555
async (workflowName: string, nodeName: string, integrationId: string) => {
56-
await fetch("/api/connections", {
56+
await fetch("/dev/api/connections", {
5757
method: "DELETE",
5858
headers: { "Content-Type": "application/json" },
5959
body: JSON.stringify({
@@ -105,7 +105,7 @@ export function useNangoIntegrations() {
105105
useEffect(() => {
106106
(async () => {
107107
try {
108-
const res = await fetch("/api/nango/integrations");
108+
const res = await fetch("/dev/api/nango/integrations");
109109
if (res.ok) {
110110
setIntegrations(await res.json());
111111
}
@@ -128,7 +128,7 @@ export function useNangoConnections(integrationId: string | null, mutationVersio
128128
if (!integrationId) return [];
129129
setLoading(true);
130130
try {
131-
const res = await fetch(`/api/nango/connections/${encodeURIComponent(integrationId)}`);
131+
const res = await fetch(`/dev/api/nango/connections/${encodeURIComponent(integrationId)}`);
132132
if (res.ok) {
133133
const data: NangoConnection[] = await res.json();
134134
setNangoConnections(data);

packages/core/dev-ui/src/hooks/useDAGSocket.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export function useDAGSocket() {
2323

2424
const connect = useCallback(() => {
2525
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
26-
const ws = new WebSocket(`${protocol}//${window.location.host}`);
26+
const ws = new WebSocket(`${protocol}//${window.location.host}/dev/ws`);
2727
wsRef.current = ws;
2828

2929
ws.onopen = () => {

packages/core/dev-ui/src/hooks/useDeploy.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export function useDeploy() {
2121
// Fetch deploy URL and freshness on mount, then poll every 30s
2222
useEffect(() => {
2323
const check = () => {
24-
fetch("/api/deploy")
24+
fetch("/dev/api/deploy")
2525
.then((r) => r.json() as Promise<{ deployed: boolean; url?: string; freshness?: DeployFreshness }>)
2626
.then((data) => {
2727
if (data.deployed && data.url) {
@@ -40,7 +40,7 @@ export function useDeploy() {
4040
setState({ status: "deploying", message: "Starting deploy..." });
4141

4242
try {
43-
const res = await fetch("/api/deploy", { method: "POST" });
43+
const res = await fetch("/dev/api/deploy", { method: "POST" });
4444

4545
if (!res.ok) {
4646
setState({ status: "error", error: `Deploy request failed (HTTP ${res.status})` });

0 commit comments

Comments
 (0)