Skip to content

Commit d46c550

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents f9b8169 + 753b893 commit d46c550

25 files changed

Lines changed: 1216 additions & 310 deletions

frontend/apps/studio/package.json

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"@nubase/ui": "workspace:*",
1818
"@tanstack/react-query": "^5.59.16",
1919
"lucide-react": "^0.453.0",
20-
"next": "14.2.16",
20+
"next": "14.2.35",
2121
"react": "^18.3.1",
2222
"react-dom": "^18.3.1",
2323
"zod": "^3.23.8",
@@ -30,17 +30,14 @@
3030
"@testing-library/jest-dom": "^6.5.0",
3131
"@testing-library/react": "^16.0.1",
3232
"@testing-library/user-event": "^14.5.2",
33-
"@types/node": "^22.7.5",
34-
"@types/react": "^18.3.12",
35-
"@types/react-dom": "^18.3.1",
3633
"@vitejs/plugin-react": "^4.3.3",
3734
"autoprefixer": "^10.4.20",
3835
"eslint": "^9.13.0",
39-
"eslint-config-next": "14.2.16",
36+
"eslint-config-next": "14.2.35",
4037
"jsdom": "^25.0.1",
41-
"postcss": "^8.4.47",
38+
"postcss": "^8.5.10",
4239
"tailwindcss": "^3.4.14",
4340
"typescript": "^5.5.4",
44-
"vitest": "^2.1.4"
41+
"vitest": "^3.2.6"
4542
}
4643
}

frontend/apps/www/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"clsx": "^2.1.1",
1919
"lucide-react": "^0.453.0",
2020
"motion": "^12.40.0",
21-
"next": "14.2.16",
21+
"next": "14.2.35",
2222
"next-themes": "^0.4.6",
2323
"react": "^18.3.1",
2424
"react-dom": "^18.3.1",
@@ -31,8 +31,8 @@
3131
"@types/react-dom": "^18.3.1",
3232
"autoprefixer": "^10.4.20",
3333
"eslint": "^9.13.0",
34-
"eslint-config-next": "14.2.16",
35-
"postcss": "^8.4.47",
34+
"eslint-config-next": "14.2.35",
35+
"postcss": "^8.5.10",
3636
"tailwindcss": "^3.4.14",
3737
"typescript": "^5.5.4"
3838
}

frontend/package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,13 @@
1313
"devDependencies": {
1414
"typescript": "^5.5.4",
1515
"prettier": "^3.3.3"
16+
},
17+
"pnpm": {
18+
"overrides": {
19+
"dompurify": ">=3.4.9",
20+
"glob@>=10.2.0 <10.5.0": "10.5.0",
21+
"postcss@<8.5.10": "^8.5.10",
22+
"vite@<6.4.2": "^6.4.2"
23+
}
1624
}
1725
}

frontend/packages/mcp-bridge/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"access": "public"
2525
},
2626
"optionalDependencies": {
27-
"esbuild": "0.21.5"
27+
"esbuild": "0.25.0"
2828
},
2929
"devDependencies": {
3030
"@types/node": "^22.7.5",

frontend/packages/mcp-bridge/skills/nubase/SKILL.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ Core:
5858

5959
Backend ops (read), in module order (Database, Auth, Storage, AI Gateway): `db_export_schema`, `db_list_migrations`, `auth_list_users`, `auth_get_settings`, `storage_list_buckets`, `storage_list_objects`, `storage_create_signed_url`, `storage_create_signed_urls`, `gateway_list_keys`, `gateway_usage`, `gateway_usage_daily`, `gateway_usage_by_model`, `gateway_usage_logs`, `gateway_pricing`.
6060

61-
Deploy (read), in module order (Assets, Functions, cron): `assets_list`, `functions_list`, `functions_logs`, `functions_secrets_list`, `cron_list`, `cron_get`, `cron_runs`.
61+
Deploy (read), in module order (Assets, Functions, cron, App workers): `assets_list`, `functions_list`, `functions_logs`, `functions_secrets_list`, `cron_list`, `cron_get`, `cron_runs`, `app_workers_list`, `app_worker_status`.
6262

6363
Backend ops (write, gated by `NUBASE_ALLOW_ADMIN_WRITE=true`): `auth_create_user`, `auth_delete_user`, `auth_update_settings`, `auth_clear_settings`, `storage_create_bucket`, `storage_delete_bucket`, `storage_create_signed_upload_url`, `gateway_issue_key`, `gateway_revoke_key`.
6464

65-
Deploy (write, gated by `NUBASE_ALLOW_ADMIN_WRITE=true`): `deploy_app`, `deployment_rollback`, `assets_upload`, `assets_delete`, `assets_update_settings`, `functions_new`, `functions_deploy`, `functions_invoke`, `functions_delete`, `functions_secrets_set`, `cron_create`, `cron_update`, `cron_delete`.
65+
Deploy (write, gated by `NUBASE_ALLOW_ADMIN_WRITE=true`): `deploy_app`, `deployment_rollback`, `assets_upload`, `assets_delete`, `assets_update_settings`, `functions_new`, `functions_deploy`, `functions_invoke`, `functions_delete`, `functions_secrets_set`, `cron_create`, `cron_update`, `cron_delete`, `app_worker_delete`.
6666

6767
Project lifecycle (platform auth required with `NUBASE_PLATFORM_JWT` or `NUBASE_PLATFORM_KEY`): `projects_list`, `project_keys_admin`, `project_select_instructions`. `project_provision` and `project_update` are also gated by `NUBASE_ALLOW_ADMIN_WRITE=true`. Project delete is intentionally not exposed.
6868

@@ -142,4 +142,5 @@ Use these focused references when the task is clearly scoped:
142142
- `references/ai-gateway.md`
143143
- `references/memory.md`
144144
- `references/cron.md` — schedule recurring jobs
145+
- `references/app-workers.md` — deploy & manage full app workers (server + bundled assets)
145146
- `references/security.md`
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Nubase App Workers Reference
2+
3+
Use this reference when shipping or managing a **full app worker** — a generated app deployed as a single Cloudflare Worker (server module + bundled static assets) onto Nubase's Workers-for-Platforms dispatch namespace, reachable at its own preview host.
4+
5+
App workers are the **server-rendered / full-stack** deploy target. Unlike `assets_*` (static frontend only) or `functions_*` (individual edge handlers), one app worker bundles a server entry module **and** its client `dist/` into a single isolated deployment per project.
6+
7+
## When to use which deploy target
8+
9+
- Static frontend only (HTML/CSS/JS) → **Assets** (`assets_*`, `references/assets.md`).
10+
- A few backend handlers → **Functions** (`functions_*`, `references/functions.md`).
11+
- A whole app shipped as one server worker (SSR / API + bundled client) → **App worker** (this doc).
12+
13+
## Deploy
14+
15+
Deploying an app worker uploads a server bundle plus optional client assets. Because it is a multipart file upload, it runs through the CLI / deploy pipeline rather than a single MCP argument:
16+
17+
```http
18+
POST /deployments/admin/v1/app-workers/deploy (multipart/form-data, service_role)
19+
metadata = JSON { appCode, version, workerName?, mainModule, serverEntrypointPath?,
20+
clientDistPath?, previewHost?, compatibilityDate?, compatibilityFlags?,
21+
envBindings?, plainTextBindings?, secretTextBindings? }
22+
serverFile = one or more JS module files (must include mainModule)
23+
assetFile = zero or more static asset files (served via the worker's ASSETS binding)
24+
```
25+
26+
- `workerName` defaults to `appCode`; `previewHost` defaults to `<workerName>.ottermind.app`.
27+
- `metadata.appCode` must match the calling project context (enforced server-side).
28+
- A custom `workerName` must equal `appCode` or be namespaced under it (`<appCode>-<suffix>`, e.g. `appabc-preview`). The dispatch namespace is shared across tenants, so a worker name outside your appCode is rejected with 403.
29+
- `NUBASE_PROJECT_REF` and `NUBASE_APP_VERSION` are injected as bindings automatically and cannot be overridden; pass app config via `plainTextBindings` and secrets via `secretTextBindings`.
30+
- The deploy is recorded in the project's deployment history (`deployments_list` / `deployment_status`).
31+
32+
## Manage (after deploy)
33+
34+
Management is **scoped to workers this project has deployed** — the dispatch namespace is shared across all tenants, so list/status/delete only ever resolve workers found in *this* project's deployment history. You cannot read or delete another project's worker.
35+
36+
Read (always available):
37+
38+
- `app_workers_list()` — list this project's deployed app workers, each with `workerName`, latest `version`, `previewHost`, `publicUrl`, `lastDeploymentStatus`, `lastDeploymentId`, and timestamps.
39+
- `app_worker_status({ workerName })` — one worker enriched with **live provider state**: returns `{ worker, existsOnProvider, provider }`, where `existsOnProvider:false` means the worker is no longer present on Cloudflare (e.g. deleted out-of-band).
40+
41+
Write (gated by `NUBASE_ALLOW_ADMIN_WRITE=true` and the project's service_role key; otherwise returns `{ success: false, ... }` without touching the backend):
42+
43+
- `app_worker_delete({ workerName })` — undeploy one app worker. Idempotent (deleting an already-absent worker still succeeds) and records an `app_worker_delete` entry in deployment history for audit. Returns `{ workerName, deleted, auditDeploymentId }`.
44+
45+
Equivalent control-plane HTTP endpoints (service_role):
46+
47+
```http
48+
GET /deployments/admin/v1/app-workers
49+
GET /deployments/admin/v1/app-workers/{workerName}
50+
DELETE /deployments/admin/v1/app-workers/{workerName}
51+
```
52+
53+
## Worked Example: inspect then retire a worker
54+
55+
```text
56+
app_workers_list()
57+
# → [{ workerName: "my-app", version: "2", publicUrl: "https://my-app.ottermind.app", lastDeploymentStatus: "succeeded", ... }]
58+
59+
app_worker_status({ "workerName": "my-app" })
60+
# → { worker: {...}, existsOnProvider: true, provider: { ...live Cloudflare script details... } }
61+
62+
app_worker_delete({ "workerName": "my-app" }) # needs NUBASE_ALLOW_ADMIN_WRITE=true + service_role
63+
# → { workerName: "my-app", deleted: true, auditDeploymentId: "..." }
64+
```
65+
66+
## Safety
67+
68+
- Deleting an app worker takes the live app offline immediately — ask the user before calling `app_worker_delete`, the same as any destructive deploy op.
69+
- Never put service_role keys or other secrets in `plainTextBindings` (those are plain text) or in bundled `assetFile` contents (assets are public). Use `secretTextBindings` for secrets.
70+
- After deploying or retiring an app worker, `memory_write` the durable facts (worker name, preview host, version).

frontend/packages/mcp-bridge/src/nubase-client.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,24 @@ export class NubaseClient {
585585
);
586586
}
587587

588+
// --- App workers control plane (/deployments/admin/v1/app-workers) -------
589+
590+
appWorkersList() {
591+
return this.request('/deployments/admin/v1/app-workers');
592+
}
593+
594+
appWorkerStatus(args: Record<string, unknown>) {
595+
const workerName = requiredString(args.workerName, 'workerName');
596+
return this.request(`/deployments/admin/v1/app-workers/${encodeURIComponent(workerName)}`);
597+
}
598+
599+
appWorkerDelete(args: Record<string, unknown>) {
600+
const workerName = requiredString(args.workerName, 'workerName');
601+
return this.guardedWrite('delete app worker', () =>
602+
this.request(`/deployments/admin/v1/app-workers/${encodeURIComponent(workerName)}`, { method: 'DELETE' })
603+
);
604+
}
605+
588606
// --- Project lifecycle control plane (/auth/v1/admin/projects) ----------
589607

590608
projectsList() {

frontend/packages/mcp-bridge/src/tools.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,25 @@ const TOOL_TABLE: Record<string, ToolEntry> = {
135135
}, ['id']),
136136
handler: (args, _config, client) => client.deploymentRollback(args),
137137
},
138+
app_workers_list: {
139+
description: 'List the app workers (Cloudflare Workers) this project has deployed, with their latest version, preview URL and status. Scoped to the current project. Read-only.',
140+
inputSchema: objectSchema({}),
141+
handler: (_args, _config, client) => client.appWorkersList(),
142+
},
143+
app_worker_status: {
144+
description: 'Get one deployed app worker for this project, enriched with live provider state. Read-only.',
145+
inputSchema: objectSchema({
146+
workerName: { type: 'string' },
147+
}, ['workerName']),
148+
handler: (args, _config, client) => client.appWorkerStatus(args),
149+
},
150+
app_worker_delete: {
151+
description: 'Delete (undeploy) one app worker owned by this project. Write op; disabled unless NUBASE_ALLOW_ADMIN_WRITE=true. Only affects workers this project has deployed.',
152+
inputSchema: objectSchema({
153+
workerName: { type: 'string' },
154+
}, ['workerName']),
155+
handler: (args, _config, client) => client.appWorkerDelete(args),
156+
},
138157
memory_context: {
139158
description: 'Return compact relevant memory context for a task. Scope defaults can come from NUBASE_USER_ID, NUBASE_AGENT_ID, and NUBASE_RUN_ID.',
140159
inputSchema: objectSchema({

frontend/packages/mcp-bridge/test/deploy-app.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ test('MCP tool list includes deploy_app', () => {
2525
assert.equal(names.has('deployment_status'), true);
2626
assert.equal(names.has('deployment_logs'), true);
2727
assert.equal(names.has('deployment_rollback'), true);
28+
assert.equal(names.has('app_workers_list'), true);
29+
assert.equal(names.has('app_worker_status'), true);
30+
assert.equal(names.has('app_worker_delete'), true);
2831
assert.equal(names.has('projects_list'), true);
2932
assert.equal(names.has('project_keys_admin'), true);
3033
assert.equal(names.has('project_provision'), true);
@@ -43,6 +46,36 @@ test('MCP tool list includes deploy_app', () => {
4346
assert.equal(names.has('gateway_pricing'), true);
4447
});
4548

49+
test('app worker tools dispatch to the matching client method', async () => {
50+
const calls: Array<{ op: string; args?: Record<string, unknown> }> = [];
51+
const client = {
52+
appWorkersList: async () => {
53+
calls.push({ op: 'appWorkersList' });
54+
return [{ workerName: 'my-app' }];
55+
},
56+
appWorkerStatus: async (args: Record<string, unknown>) => {
57+
calls.push({ op: 'appWorkerStatus', args });
58+
return { worker: { workerName: args.workerName }, existsOnProvider: true };
59+
},
60+
appWorkerDelete: async (args: Record<string, unknown>) => {
61+
calls.push({ op: 'appWorkerDelete', args });
62+
return { workerName: args.workerName, deleted: true };
63+
},
64+
} as unknown as Parameters<typeof callTool>[3];
65+
66+
const listed = await callTool('app_workers_list', {}, config(), client) as Array<Record<string, unknown>>;
67+
assert.equal(listed[0]?.workerName, 'my-app');
68+
69+
const status = await callTool('app_worker_status', { workerName: 'my-app' }, config(), client) as Record<string, any>;
70+
assert.equal(status.existsOnProvider, true);
71+
72+
const deleted = await callTool('app_worker_delete', { workerName: 'my-app' }, config(), client) as Record<string, any>;
73+
assert.equal(deleted.deleted, true);
74+
75+
assert.deepEqual(calls.map((call) => call.op), ['appWorkersList', 'appWorkerStatus', 'appWorkerDelete']);
76+
assert.equal(calls[1]?.args?.workerName, 'my-app');
77+
});
78+
4679
test('deploy_app orchestrates migrations, functions, assets, cron, and memory', async () => {
4780
const root = await mkdtemp(path.join(os.tmpdir(), 'nubase-deploy-app-'));
4881
await mkdir(path.join(root, 'dist/css'), { recursive: true });

0 commit comments

Comments
 (0)