skybridge 0.0.0-dev.e3c0d0b → 0.0.0-dev.e3dc6b1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -113
- package/dist/cli/build-helpers.d.ts +7 -0
- package/dist/cli/build-helpers.js +82 -0
- package/dist/cli/build-helpers.js.map +1 -0
- package/dist/cli/build-helpers.test.d.ts +1 -0
- package/dist/cli/build-helpers.test.js +64 -0
- package/dist/cli/build-helpers.test.js.map +1 -0
- package/dist/cli/detect-port.d.ts +2 -2
- package/dist/cli/detect-port.js +9 -20
- package/dist/cli/detect-port.js.map +1 -1
- package/dist/cli/header.js.map +1 -1
- package/dist/cli/resolve-views-dir.d.ts +1 -0
- package/dist/cli/resolve-views-dir.js +17 -0
- package/dist/cli/resolve-views-dir.js.map +1 -0
- package/dist/cli/run-command.js.map +1 -1
- package/dist/cli/telemetry.js.map +1 -1
- package/dist/cli/tunnel-control-server.js.map +1 -1
- package/dist/cli/tunnel-control-server.test.js.map +1 -1
- package/dist/cli/tunnel-handler.js.map +1 -1
- package/dist/cli/tunnel-handler.test.js.map +1 -1
- package/dist/cli/tunnel.js +2 -2
- package/dist/cli/tunnel.js.map +1 -1
- package/dist/cli/tunnel.test.js.map +1 -1
- package/dist/cli/types.js.map +1 -1
- package/dist/cli/use-execute-steps.js.map +1 -1
- package/dist/cli/use-messages.js.map +1 -1
- package/dist/cli/use-nodemon.js +11 -2
- package/dist/cli/use-nodemon.js.map +1 -1
- package/dist/cli/use-open-browser.d.ts +1 -0
- package/dist/cli/use-open-browser.js +44 -0
- package/dist/cli/use-open-browser.js.map +1 -0
- package/dist/cli/use-open-tunnel-browser.d.ts +6 -0
- package/dist/cli/use-open-tunnel-browser.js +19 -0
- package/dist/cli/use-open-tunnel-browser.js.map +1 -0
- package/dist/cli/use-tunnel.js.map +1 -1
- package/dist/cli/use-typescript-check.js +1 -1
- package/dist/cli/use-typescript-check.js.map +1 -1
- package/dist/commands/build.d.ts +0 -1
- package/dist/commands/build.js +41 -18
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/create.d.ts +9 -0
- package/dist/commands/create.js +30 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/dev.d.ts +1 -0
- package/dist/commands/dev.js +25 -0
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/start.js +7 -1
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/telemetry/disable.js.map +1 -1
- package/dist/commands/telemetry/enable.js.map +1 -1
- package/dist/commands/telemetry/status.js.map +1 -1
- package/dist/server/asset-base-url-transform-plugin.d.ts +1 -0
- package/dist/server/asset-base-url-transform-plugin.js +16 -1
- package/dist/server/asset-base-url-transform-plugin.js.map +1 -1
- package/dist/server/asset-base-url-transform-plugin.test.js +51 -1
- package/dist/server/asset-base-url-transform-plugin.test.js.map +1 -1
- package/dist/server/auth.d.ts +20 -0
- package/dist/server/auth.js +28 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/build-manifest.test.d.ts +1 -0
- package/dist/server/build-manifest.test.js +27 -0
- package/dist/server/build-manifest.test.js.map +1 -0
- package/dist/server/content-helpers.d.ts +40 -0
- package/dist/server/content-helpers.js +33 -0
- package/dist/server/content-helpers.js.map +1 -1
- package/dist/server/content-helpers.test.js.map +1 -1
- package/dist/server/express.d.ts +1 -5
- package/dist/server/express.js +13 -7
- package/dist/server/express.js.map +1 -1
- package/dist/server/express.test.js +183 -71
- package/dist/server/express.test.js.map +1 -1
- package/dist/server/file-ref.d.ts +28 -0
- package/dist/server/file-ref.js +27 -0
- package/dist/server/file-ref.js.map +1 -0
- package/dist/server/index.d.ts +4 -2
- package/dist/server/index.js +3 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/inferUtilityTypes.js.map +1 -1
- package/dist/server/metric.js.map +1 -1
- package/dist/server/middleware.d.ts +16 -3
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/middleware.test-d.js.map +1 -1
- package/dist/server/middleware.test.js.map +1 -1
- package/dist/server/server.d.ts +214 -6
- package/dist/server/server.js +236 -72
- package/dist/server/server.js.map +1 -1
- package/dist/server/templateHelper.d.ts +0 -2
- package/dist/server/templateHelper.js +3 -22
- package/dist/server/templateHelper.js.map +1 -1
- package/dist/server/templates.generated.d.ts +4 -0
- package/dist/server/templates.generated.js +47 -0
- package/dist/server/templates.generated.js.map +1 -0
- package/dist/server/tunnel-proxy-router.js.map +1 -1
- package/dist/server/tunnel-proxy-router.test.js.map +1 -1
- package/dist/server/viewsDevServer.js.map +1 -1
- package/dist/test/utils.js.map +1 -1
- package/dist/test/view.test.js +45 -0
- package/dist/test/view.test.js.map +1 -1
- package/dist/version.js +1 -3
- package/dist/version.js.map +1 -1
- package/dist/web/bridges/apps-sdk/adaptor.d.ts +7 -2
- package/dist/web/bridges/apps-sdk/adaptor.js +27 -3
- package/dist/web/bridges/apps-sdk/adaptor.js.map +1 -1
- package/dist/web/bridges/apps-sdk/bridge.d.ts +1 -0
- package/dist/web/bridges/apps-sdk/bridge.js +1 -0
- package/dist/web/bridges/apps-sdk/bridge.js.map +1 -1
- package/dist/web/bridges/apps-sdk/index.js.map +1 -1
- package/dist/web/bridges/apps-sdk/types.d.ts +8 -1
- package/dist/web/bridges/apps-sdk/types.js.map +1 -1
- package/dist/web/bridges/apps-sdk/use-apps-sdk-context.d.ts +11 -0
- package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js +11 -0
- package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js.map +1 -1
- package/dist/web/bridges/get-adaptor.d.ts +7 -0
- package/dist/web/bridges/get-adaptor.js +7 -0
- package/dist/web/bridges/get-adaptor.js.map +1 -1
- package/dist/web/bridges/index.js.map +1 -1
- package/dist/web/bridges/mcp-app/adaptor.d.ts +7 -2
- package/dist/web/bridges/mcp-app/adaptor.js +21 -6
- package/dist/web/bridges/mcp-app/adaptor.js.map +1 -1
- package/dist/web/bridges/mcp-app/bridge.d.ts +4 -2
- package/dist/web/bridges/mcp-app/bridge.js +23 -1
- package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
- package/dist/web/bridges/mcp-app/index.js.map +1 -1
- package/dist/web/bridges/mcp-app/types.js.map +1 -1
- package/dist/web/bridges/mcp-app/use-mcp-app-context.d.ts +12 -0
- package/dist/web/bridges/mcp-app/use-mcp-app-context.js +12 -0
- package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -1
- package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js.map +1 -1
- package/dist/web/bridges/mcp-app/view-tools.test.d.ts +1 -0
- package/dist/web/bridges/mcp-app/view-tools.test.js +144 -0
- package/dist/web/bridges/mcp-app/view-tools.test.js.map +1 -0
- package/dist/web/bridges/types.d.ts +98 -3
- package/dist/web/bridges/types.js.map +1 -1
- package/dist/web/bridges/use-host-context.d.ts +5 -0
- package/dist/web/bridges/use-host-context.js +5 -0
- package/dist/web/bridges/use-host-context.js.map +1 -1
- package/dist/web/components/modal-provider.js.map +1 -1
- package/dist/web/create-store.d.ts +26 -0
- package/dist/web/create-store.js +26 -0
- package/dist/web/create-store.js.map +1 -1
- package/dist/web/create-store.test.js.map +1 -1
- package/dist/web/data-llm.d.ts +33 -0
- package/dist/web/data-llm.js +28 -0
- package/dist/web/data-llm.js.map +1 -1
- package/dist/web/data-llm.test.js.map +1 -1
- package/dist/web/generate-helpers.d.ts +2 -0
- package/dist/web/generate-helpers.js +2 -0
- package/dist/web/generate-helpers.js.map +1 -1
- package/dist/web/generate-helpers.test-d.js.map +1 -1
- package/dist/web/generate-helpers.test.js.map +1 -1
- package/dist/web/helpers/state.js.map +1 -1
- package/dist/web/helpers/state.test.js.map +1 -1
- package/dist/web/hooks/index.d.ts +4 -0
- package/dist/web/hooks/index.js +4 -0
- package/dist/web/hooks/index.js.map +1 -1
- package/dist/web/hooks/test/utils.d.ts +6 -2
- package/dist/web/hooks/test/utils.js +13 -2
- package/dist/web/hooks/test/utils.js.map +1 -1
- package/dist/web/hooks/use-call-tool.d.ts +45 -0
- package/dist/web/hooks/use-call-tool.js +28 -0
- package/dist/web/hooks/use-call-tool.js.map +1 -1
- package/dist/web/hooks/use-call-tool.test-d.js.map +1 -1
- package/dist/web/hooks/use-call-tool.test.js +27 -6
- package/dist/web/hooks/use-call-tool.test.js.map +1 -1
- package/dist/web/hooks/use-display-mode.d.ts +20 -0
- package/dist/web/hooks/use-display-mode.js +20 -0
- package/dist/web/hooks/use-display-mode.js.map +1 -1
- package/dist/web/hooks/use-display-mode.test-d.js.map +1 -1
- package/dist/web/hooks/use-display-mode.test.js.map +1 -1
- package/dist/web/hooks/use-download.d.ts +5 -0
- package/dist/web/hooks/use-download.js +8 -0
- package/dist/web/hooks/use-download.js.map +1 -0
- package/dist/web/hooks/use-download.test.d.ts +1 -0
- package/dist/web/hooks/use-download.test.js +95 -0
- package/dist/web/hooks/use-download.test.js.map +1 -0
- package/dist/web/hooks/use-files.d.ts +32 -0
- package/dist/web/hooks/use-files.js +32 -0
- package/dist/web/hooks/use-files.js.map +1 -1
- package/dist/web/hooks/use-files.test.js.map +1 -1
- package/dist/web/hooks/use-layout.d.ts +2 -0
- package/dist/web/hooks/use-layout.js +2 -0
- package/dist/web/hooks/use-layout.js.map +1 -1
- package/dist/web/hooks/use-layout.test.js.map +1 -1
- package/dist/web/hooks/use-open-external.d.ts +17 -0
- package/dist/web/hooks/use-open-external.js +16 -0
- package/dist/web/hooks/use-open-external.js.map +1 -1
- package/dist/web/hooks/use-open-external.test.js.map +1 -1
- package/dist/web/hooks/use-register-view-tool.d.ts +38 -0
- package/dist/web/hooks/use-register-view-tool.js +50 -0
- package/dist/web/hooks/use-register-view-tool.js.map +1 -0
- package/dist/web/hooks/use-request-close.d.ts +16 -0
- package/dist/web/hooks/use-request-close.js +21 -0
- package/dist/web/hooks/use-request-close.js.map +1 -0
- package/dist/web/hooks/use-request-close.test.d.ts +1 -0
- package/dist/web/hooks/use-request-close.test.js +52 -0
- package/dist/web/hooks/use-request-close.test.js.map +1 -0
- package/dist/web/hooks/use-request-modal.d.ts +16 -1
- package/dist/web/hooks/use-request-modal.js +16 -1
- package/dist/web/hooks/use-request-modal.js.map +1 -1
- package/dist/web/hooks/use-request-modal.test.js.map +1 -1
- package/dist/web/hooks/use-request-size.d.ts +20 -0
- package/dist/web/hooks/use-request-size.js +24 -0
- package/dist/web/hooks/use-request-size.js.map +1 -0
- package/dist/web/hooks/use-request-size.test.d.ts +1 -0
- package/dist/web/hooks/use-request-size.test.js +65 -0
- package/dist/web/hooks/use-request-size.test.js.map +1 -0
- package/dist/web/hooks/use-send-follow-up-message.d.ts +19 -1
- package/dist/web/hooks/use-send-follow-up-message.js +19 -2
- package/dist/web/hooks/use-send-follow-up-message.js.map +1 -1
- package/dist/web/hooks/use-set-open-in-app-url.d.ts +17 -0
- package/dist/web/hooks/use-set-open-in-app-url.js +17 -0
- package/dist/web/hooks/use-set-open-in-app-url.js.map +1 -1
- package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
- package/dist/web/hooks/use-tool-info.d.ts +33 -0
- package/dist/web/hooks/use-tool-info.js +26 -0
- package/dist/web/hooks/use-tool-info.js.map +1 -1
- package/dist/web/hooks/use-tool-info.test-d.js.map +1 -1
- package/dist/web/hooks/use-tool-info.test.js.map +1 -1
- package/dist/web/hooks/use-user.d.ts +2 -0
- package/dist/web/hooks/use-user.js +2 -0
- package/dist/web/hooks/use-user.js.map +1 -1
- package/dist/web/hooks/use-user.test.js.map +1 -1
- package/dist/web/hooks/use-view-state.d.ts +21 -0
- package/dist/web/hooks/use-view-state.js.map +1 -1
- package/dist/web/hooks/use-view-state.test.js.map +1 -1
- package/dist/web/index.js.map +1 -1
- package/dist/web/mount-view.d.ts +19 -0
- package/dist/web/mount-view.js +19 -0
- package/dist/web/mount-view.js.map +1 -1
- package/dist/web/plugin/data-llm.test.js.map +1 -1
- package/dist/web/plugin/plugin.d.ts +28 -0
- package/dist/web/plugin/plugin.js +35 -9
- package/dist/web/plugin/plugin.js.map +1 -1
- package/dist/web/plugin/scan-views.js.map +1 -1
- package/dist/web/plugin/scan-views.test.js.map +1 -1
- package/dist/web/plugin/transform-data-llm.js.map +1 -1
- package/dist/web/plugin/transform-data-llm.test.js.map +1 -1
- package/dist/web/plugin/validate-view.js.map +1 -1
- package/dist/web/plugin/validate-view.test.js.map +1 -1
- package/dist/web/proxy.js.map +1 -1
- package/dist/web/types.d.ts +4 -0
- package/dist/web/types.js.map +1 -1
- package/package.json +15 -6
- package/dist/server/templates/development.hbs +0 -12
- package/dist/server/templates/production.hbs +0 -6
package/README.md
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
# Skybridge - the MCP Apps framework
|
|
2
|
+
|
|
1
3
|
<p align="center">
|
|
2
|
-
<a href="https://skybridge.tech">
|
|
3
|
-
<
|
|
4
|
+
<a href="https://docs.skybridge.tech">
|
|
5
|
+
<picture>
|
|
6
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/alpic-ai/skybridge/main/docs/images/skybridge-readme-banner-dark.png" />
|
|
7
|
+
<img alt="Skybridge, the full-stack React framework for MCP apps and MCP servers" src="https://raw.githubusercontent.com/alpic-ai/skybridge/main/docs/images/skybridge-readme-banner-light.png" width="100%" />
|
|
8
|
+
</picture>
|
|
4
9
|
</a>
|
|
5
10
|
</p>
|
|
6
11
|
|
|
@@ -11,170 +16,137 @@
|
|
|
11
16
|
<p align="center">
|
|
12
17
|
<a href="https://docs.skybridge.tech">Documentation</a> ·
|
|
13
18
|
<a href="https://docs.skybridge.tech/quickstart/create-new-app">Quickstart</a> ·
|
|
14
|
-
<a href="https://github.com/alpic-ai/skybridge/tree/main/examples">Examples</a>
|
|
15
|
-
<a href="https://docs.skybridge.tech/showcase">Showcase</a>
|
|
19
|
+
<a href="https://github.com/alpic-ai/skybridge/tree/main/examples">Examples</a>
|
|
16
20
|
</p>
|
|
17
21
|
|
|
18
22
|
<p align="center">
|
|
19
|
-
<
|
|
23
|
+
<a href="https://www.npmjs.com/package/skybridge"><picture><source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/npm/v/skybridge?color=77F5EE&labelColor=161B22&style=for-the-badge"><img alt="npm version" src="https://img.shields.io/npm/v/skybridge?color=E3FAF7&labelColor=F6F8FA&style=for-the-badge"></picture></a>
|
|
24
|
+
<a href="https://www.npmjs.com/package/skybridge"><picture><source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/npm/dm/skybridge?color=D7FFC8&labelColor=161B22&style=for-the-badge"><img alt="npm downloads" src="https://img.shields.io/npm/dm/skybridge?color=E8FBD9&labelColor=F6F8FA&style=for-the-badge"></picture></a>
|
|
25
|
+
<a href="https://discord.com/invite/gNAazGueab"><picture><source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/badge/Discord-community-77F5EE?style=for-the-badge&logo=discord&logoColor=77F5EE&labelColor=161B22"><img alt="Discord community" src="https://img.shields.io/badge/Discord-community-E3FAF7?style=for-the-badge&logo=discord&logoColor=5865F2&labelColor=F6F8FA"></picture></a>
|
|
26
|
+
<a href="https://github.com/alpic-ai/skybridge/blob/main/LICENSE"><picture><source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/github/license/alpic-ai/skybridge?color=D7FFC8&labelColor=161B22&style=for-the-badge"><img alt="License: MIT" src="https://img.shields.io/github/license/alpic-ai/skybridge?color=E8FBD9&labelColor=F6F8FA&style=for-the-badge"></picture></a>
|
|
20
27
|
</p>
|
|
21
28
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
</a>
|
|
29
|
-
<a href="https://www.npmjs.com/package/skybridge">
|
|
30
|
-
<picture>
|
|
31
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/npm/dm/skybridge?color=D7FFC8&labelColor=161B22&style=for-the-badge">
|
|
32
|
-
<img alt="npm downloads" src="https://img.shields.io/npm/dm/skybridge?color=E8FBD9&labelColor=F6F8FA&style=for-the-badge">
|
|
33
|
-
</picture>
|
|
34
|
-
</a>
|
|
35
|
-
<a href="https://discord.com/invite/gNAazGueab">
|
|
36
|
-
<picture>
|
|
37
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/badge/Discord-community-77F5EE?style=for-the-badge&logo=discord&logoColor=77F5EE&labelColor=161B22">
|
|
38
|
-
<img alt="Discord community" src="https://img.shields.io/badge/Discord-community-E3FAF7?style=for-the-badge&logo=discord&logoColor=5865F2&labelColor=F6F8FA">
|
|
39
|
-
</picture>
|
|
40
|
-
</a>
|
|
41
|
-
<a href="https://github.com/alpic-ai/skybridge/blob/main/LICENSE">
|
|
42
|
-
<picture>
|
|
43
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/github/license/alpic-ai/skybridge?color=D7FFC8&labelColor=161B22&style=for-the-badge">
|
|
44
|
-
<img alt="License: MIT" src="https://img.shields.io/github/license/alpic-ai/skybridge?color=E8FBD9&labelColor=F6F8FA&style=for-the-badge">
|
|
45
|
-
</picture>
|
|
46
|
-
</a>
|
|
47
|
-
</p>
|
|
29
|
+
## About Skybridge
|
|
30
|
+
|
|
31
|
+
Skybridge helps developers build type-safe MCP apps for Claude, ChatGPT and other UI-enabled MCP clients, with a complete set of tooling designed for both humans and agents.
|
|
32
|
+
|
|
33
|
+
Why? MCP apps extend the [Model Context Protocol](https://modelcontextprotocol.io/docs/getting-started/intro) with **rich, interactive UI views** rendered from MCP servers. Conversational apps need seamless interaction between the user, the UI, and the model. This means new UX patterns, developer tooling, and abstractions.
|
|
34
|
+
Plus, the raw SDKs are low-level: no hooks, type safety, HMR, etc.
|
|
48
35
|
|
|
49
|
-
|
|
36
|
+
That's why we built *Skybridge*.
|
|
50
37
|
|
|
51
|
-
|
|
38
|
+
Features include:
|
|
39
|
+
|
|
40
|
+
- **Delightful dev environment**: Skybridge provides a dev server with a local emulator, hot module reload, and a permanent tunnel to connect your local app to Claude and ChatGPT.
|
|
41
|
+
- **Write once, run everywhere**: the framework abstracts implementation differences between MCP clients, so your app runs seamlessly in Claude, ChatGPT, VSCode, and any other MCP apps compatible client.
|
|
42
|
+
- **Agent-ready**: powerful skills, CLI, and programmatic dev tool APIs, everything your coding agent needs to build MCP apps end-to-end.
|
|
43
|
+
- **Type-safe end-to-end**: tRPC-style inference from MCP server tool definition to React view for type safety from server to frontend.
|
|
44
|
+
- **React-first**: Intuitive React Query-style hooks, with advanced state management.
|
|
45
|
+
- **Example library**: get started quickly with ChatGPT- and Claude-ready app examples for ecommerce, travel, SaaS, and more.
|
|
46
|
+
|
|
47
|
+
They chose to build their MCP apps with Skybridge:
|
|
52
48
|
|
|
53
49
|
<p align="center">
|
|
54
|
-
<a href="https://www.datadoghq.com">
|
|
55
|
-
<picture>
|
|
56
|
-
<source media="(prefers-color-scheme: dark)" srcset="docs/images/user-logos/datadog-dark.svg">
|
|
57
|
-
<img src="docs/images/user-logos/datadog-light.svg" alt="Datadog" height="24">
|
|
58
|
-
</picture>
|
|
59
|
-
</a>
|
|
50
|
+
<a href="https://www.datadoghq.com"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/alpic-ai/skybridge/main/docs/images/user-logos/datadog-dark.svg"><img src="https://raw.githubusercontent.com/alpic-ai/skybridge/main/docs/images/user-logos/datadog-light.svg" alt="Datadog" height="24"></picture></a>
|
|
60
51
|
|
|
61
|
-
<a href="https://bitmovin.com">
|
|
62
|
-
<picture>
|
|
63
|
-
<source media="(prefers-color-scheme: dark)" srcset="docs/images/user-logos/bitmovin-dark.svg">
|
|
64
|
-
<img src="docs/images/user-logos/bitmovin-light.svg" alt="Bitmovin" height="22">
|
|
65
|
-
</picture>
|
|
66
|
-
</a>
|
|
52
|
+
<a href="https://bitmovin.com"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/alpic-ai/skybridge/main/docs/images/user-logos/bitmovin-dark.svg"><img src="https://raw.githubusercontent.com/alpic-ai/skybridge/main/docs/images/user-logos/bitmovin-light.svg" alt="Bitmovin" height="22"></picture></a>
|
|
67
53
|
|
|
68
|
-
<a href="https://www.evaneos.com">
|
|
69
|
-
<picture>
|
|
70
|
-
<source media="(prefers-color-scheme: dark)" srcset="docs/images/user-logos/evaneos-dark.svg">
|
|
71
|
-
<img src="docs/images/user-logos/evaneos-light.svg" alt="Evaneos" height="18">
|
|
72
|
-
</picture>
|
|
73
|
-
</a>
|
|
54
|
+
<a href="https://www.evaneos.com"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/alpic-ai/skybridge/main/docs/images/user-logos/evaneos-dark.svg"><img src="https://raw.githubusercontent.com/alpic-ai/skybridge/main/docs/images/user-logos/evaneos-light.svg" alt="Evaneos" height="18"></picture></a>
|
|
74
55
|
|
|
75
|
-
<a href="https://www.touchstream.media">
|
|
76
|
-
<picture>
|
|
77
|
-
<source media="(prefers-color-scheme: dark)" srcset="docs/images/user-logos/touchstream-dark.svg">
|
|
78
|
-
<img src="docs/images/user-logos/touchstream-light.svg" alt="Touchstream" height="24">
|
|
79
|
-
</picture>
|
|
80
|
-
</a>
|
|
56
|
+
<a href="https://www.touchstream.media"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/alpic-ai/skybridge/main/docs/images/user-logos/touchstream-dark.svg"><img src="https://raw.githubusercontent.com/alpic-ai/skybridge/main/docs/images/user-logos/touchstream-light.svg" alt="Touchstream" height="24"></picture></a>
|
|
81
57
|
|
|
82
|
-
<a href="https://www.cottages.com">
|
|
83
|
-
<picture>
|
|
84
|
-
<source media="(prefers-color-scheme: dark)" srcset="docs/images/user-logos/cottages-dark.svg">
|
|
85
|
-
<img src="docs/images/user-logos/cottages-light.svg" alt="Cottages.com" height="24">
|
|
86
|
-
</picture>
|
|
87
|
-
</a>
|
|
58
|
+
<a href="https://www.cottages.com"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/alpic-ai/skybridge/main/docs/images/user-logos/cottages-dark.svg"><img src="https://raw.githubusercontent.com/alpic-ai/skybridge/main/docs/images/user-logos/cottages-light.svg" alt="Cottages.com" height="24"></picture></a>
|
|
88
59
|
</p>
|
|
89
60
|
|
|
90
|
-
## Why Skybridge?
|
|
91
|
-
|
|
92
|
-
MCP Apps is an extension of the [Model Context Protocol](https://modelcontextprotocol.io/docs/getting-started/intro) adding **rich, interactive UI views** to MCP servers. Building conversational Apps requires new UX design concepts, developer tooling and abstractions to build apps that make interactions between the user, the UI and the model seamless.
|
|
93
|
-
|
|
94
|
-
Skybridge is the answer to all the problems we ran into while building hundreds of production MCP Apps. Features include:
|
|
95
|
-
|
|
96
|
-
- **Delightful dev environment**: It provides a dev server with a local emulator, Hot Module Reload, and a permanent tunnel to connect your local app to Claude and ChatGPT.
|
|
97
|
-
- **Write once, run everywhere**: Skybridge abstracts implementation differences between MCP Clients, so your app runs seamlessly in Claude, ChatGPT, VSCode, and any other compatible MCP Apps client.
|
|
98
|
-
- **Agent-ready**: Powerful Skills, CLI, and programmatic DevTools APIs: it provides everything your coding agent needs to build MCP Apps end-to-end.
|
|
99
|
-
- **Type-safe end-to-end**: tRPC-style inference from MCP server tool definition to React view for type-safety end-to-end from server to frontend.
|
|
100
|
-
- **React-first**: Intuitive React Query-style hooks, with advanced state management.
|
|
101
|
-
- **Examples library**: Get started quickly with production-ready app examples for e-commerce, travel, SaaS, and others.
|
|
102
|
-
|
|
103
61
|
## Get started
|
|
104
62
|
|
|
105
63
|
**For agents**
|
|
106
64
|
|
|
107
|
-
Install our [
|
|
65
|
+
Install our [skill](https://docs.skybridge.tech/devtools/skills) for building MCP apps and ChatGPT apps:
|
|
108
66
|
```bash
|
|
109
67
|
npx skills add alpic-ai/skybridge -s skybridge
|
|
110
68
|
```
|
|
111
|
-
Once installed,
|
|
69
|
+
Once installed, ask your agent "What skills do you have?" to confirm, then try:
|
|
112
70
|
|
|
113
|
-
- _Create a new MCP
|
|
114
|
-
- _Migrate my MCP
|
|
115
|
-
- _Add a new view to my MCP
|
|
71
|
+
- _Create a new MCP app_
|
|
72
|
+
- _Migrate my MCP server to the Skybridge framework_
|
|
73
|
+
- _Add a new view to my MCP app_
|
|
116
74
|
|
|
117
75
|
**For humans**
|
|
118
76
|
|
|
119
77
|
Bootstrap a new project with:
|
|
120
78
|
```bash
|
|
121
|
-
npm create skybridge my-app
|
|
79
|
+
npm create skybridge@latest my-app
|
|
122
80
|
```
|
|
123
|
-
For full install instructions, read
|
|
81
|
+
For full install instructions, read our [**Quickstart guide**](https://docs.skybridge.tech/quickstart/create-new-app).
|
|
124
82
|
|
|
125
83
|
## Documentation
|
|
126
84
|
|
|
127
85
|
The [Skybridge documentation](https://docs.skybridge.tech) covers the full lifecycle of building MCP Apps:
|
|
128
86
|
|
|
129
87
|
- [Fundamentals](https://docs.skybridge.tech/fundamentals): understand MCP Apps, ChatGPT Apps, and how Skybridge bridges both runtimes.
|
|
130
|
-
- [Core concepts](https://docs.skybridge.tech/concepts): learn data
|
|
88
|
+
- [Core concepts](https://docs.skybridge.tech/concepts): learn about server <> model <> UI data flows, LLM context sync, type safety, and instant local iteration with our devtools.
|
|
131
89
|
- [Guides](https://docs.skybridge.tech/guides/fetching-data): build real app behavior with tools, views, state, and model communication.
|
|
132
|
-
- [API Reference](https://docs.skybridge.tech/api-reference): browse server APIs, React hooks, CLI commands, and runtime compatibility.
|
|
90
|
+
- [API Reference](https://docs.skybridge.tech/api-reference): browse our MCP server APIs, React hooks, CLI commands, and runtime compatibility.
|
|
133
91
|
|
|
134
92
|
## Deploy
|
|
135
93
|
|
|
136
|
-
Deploy Skybridge apps instantly on [Alpic](https://alpic.ai)
|
|
94
|
+
Deploy Skybridge apps instantly on [Alpic](https://alpic.ai) for scalable hosting, MCP-specific analytics, permanent tunneling, app store compliance auditing and submission help. You can also self-host on any Node.js-compatible platform.
|
|
95
|
+
|
|
96
|
+
See our [deployment guide](https://docs.skybridge.tech/quickstart/deploy) for the full production path.
|
|
137
97
|
|
|
138
|
-
|
|
98
|
+
## Community & Contributing
|
|
99
|
+
|
|
100
|
+
We'd love your help improving Skybridge. Here are a few ways to get involved:
|
|
101
|
+
|
|
102
|
+
- **Bugs**: If you run into a bug or unexpected behavior, open a [GitHub Issue](https://github.com/alpic-ai/skybridge/issues) with a clear reproduction.
|
|
103
|
+
- **Questions and ideas**: Need help building with Skybridge or have ideas to improve the framework, docs, examples, or developer experience? [Open an issue](https://github.com/alpic-ai/skybridge/issues) or share them on our [Discord](https://discord.com/invite/gNAazGueab).
|
|
104
|
+
- **Pull requests**: For code or documentation changes, read the [Contributing Guide](https://github.com/alpic-ai/skybridge/blob/main/CONTRIBUTING.md) before opening a PR.
|
|
105
|
+
|
|
106
|
+
Skybridge is released under the [MIT License](https://github.com/alpic-ai/skybridge/blob/main/LICENSE).
|
|
107
|
+
|
|
108
|
+
### Contributors
|
|
109
|
+
|
|
110
|
+
Built and maintained with ❤️ by [Harijoe](https://github.com/harijoe), [Fred Barthelet](https://github.com/fredericbarthelet), and the [Alpic](https://alpic.ai) team.
|
|
111
|
+
|
|
112
|
+
<a href="https://github.com/alpic-ai/skybridge/graphs/contributors">
|
|
113
|
+
<img src="https://contrib.rocks/image?repo=alpic-ai/skybridge" alt="Skybridge contributors">
|
|
114
|
+
</a>
|
|
139
115
|
|
|
140
116
|
## Example templates
|
|
141
117
|
|
|
142
118
|
Explore all our example templates in the [Examples](https://docs.skybridge.tech/examples) section of the documentation.
|
|
143
119
|
|
|
120
|
+
### Basic
|
|
121
|
+
|
|
122
|
+
| Preview | App | Description | Demo | Code |
|
|
123
|
+
| --- | --- | --- | --- | --- |
|
|
124
|
+
| <img src="docs/images/showcase-example.png" alt="Everything" width="160" /> | Everything | Comprehensive playground app showcasing all Skybridge hooks and features. | [Try Demo](https://everything.skybridge.tech/try) | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/everything) |
|
|
125
|
+
|
|
126
|
+
### Use cases
|
|
127
|
+
|
|
144
128
|
| Preview | App | Description | Demo | Code |
|
|
145
129
|
| --- | --- | --- | --- | --- |
|
|
146
130
|
| <img src="docs/images/showcase-capitals.png" alt="Capitals Explorer" width="160" /> | Capitals Explorer | Interactive world map with geolocation, country information, and dynamic capital exploration. | [Try Demo](https://capitals.skybridge.tech/try) | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/capitals) |
|
|
147
131
|
| <img src="docs/images/showcase-flight-booking.png" alt="Flight Booking" width="160" /> | Flight Booking | Flight search carousel with route details, pricing comparison, and external booking. | [Try Demo](https://flight-booking.skybridge.tech/try) | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/flight-booking) |
|
|
148
132
|
| <img src="docs/images/showcase-ecommerce.png" alt="Ecommerce Carousel" width="160" /> | Ecommerce Carousel | Product carousel with persistent cart, localization, theme switching, and modal dialogs. | [Try Demo](https://ecommerce.skybridge.tech/try) | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/ecom-carousel) |
|
|
149
|
-
| <img src="docs/images/showcase-
|
|
150
|
-
| <img src="docs/images/showcase-
|
|
151
|
-
| <img src="docs/images/showcase-productivity.png" alt="Productivity" width="160" /> | Productivity | Data visualization dashboard demonstrating Skybridge capabilities for MCP Apps. | [Try Demo](https://productivity.skybridge.tech/try) | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/productivity) |
|
|
133
|
+
| <img src="docs/images/showcase-investigation-game.png" alt="Investigation Game" width="160" /> | Investigation Game | Multi-screen mystery game with fullscreen mode, dynamic story progression and context asynchronicity demonstration | [Try Demo](https://investigation-game.skybridge.tech/try) | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/investigation-game) |
|
|
134
|
+
| <img src="docs/images/showcase-productivity.png" alt="Productivity" width="160" /> | Productivity | Interactive analytics dashboard with charts, theme adaptation, localization, fullscreen mode, and bidirectional tool calls. | [Try Demo](https://productivity.skybridge.tech/try) | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/productivity) |
|
|
152
135
|
| <img src="docs/images/showcase-times-up.png" alt="Time's Up" width="160" /> | Time's Up | Word-guessing party game where the user gives hints and the AI tries to guess. | [Try Demo](https://times-up.skybridge.tech/try) | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/times-up) |
|
|
153
|
-
| <img src="docs/images/showcase-manifest-ui.png" alt="Manifest UI" width="160" /> | Manifest UI | Agentic component library example for rich AI-powered experiences. | [Try Demo](https://manifest-ui.skybridge.tech/try) | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/manifest-ui) |
|
|
154
|
-
| <img src="docs/images/showcase-generative-ui.png" alt="Generative UI" width="160" /> | Generative UI | LLM-generated dynamic UIs with json-render and 36 pre-built shadcn/ui components. | [Try Demo](https://generative-ui.skybridge.tech/try) | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/generative-ui) |
|
|
155
|
-
| <img src="docs/images/showcase-magic-8-ball.png" alt="Magic 8-Ball" width="160" /> | Magic 8-Ball | Default starter app for the core Skybridge loop: input, server logic, and view rendering. | [Try Demo](https://magic-8-ball.skybridge.tech/try) | [View code](https://github.com/alpic-ai/apps-sdk-template) |
|
|
156
|
-
| <img src="docs/images/showcase-clerk.png" alt="Auth Clerk" width="160" /> | Auth — Clerk | Full OAuth authentication with Clerk and personalized coffee shop search. | — | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/auth-clerk) |
|
|
157
|
-
| <img src="docs/images/showcase-workos.png" alt="Auth WorkOS AuthKit" width="160" /> | Auth — WorkOS AuthKit | Full OAuth authentication with WorkOS AuthKit and personalized coffee shop search. | — | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/auth-workos) |
|
|
158
|
-
| <img src="docs/images/showcase-stytch.png" alt="Auth Stytch" width="160" /> | Auth — Stytch | Full OAuth authentication with Stytch and personalized coffee shop search. | — | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/auth-stytch) |
|
|
159
|
-
| <img src="docs/images/showcase-auth0.png" alt="Auth Auth0" width="160" /> | Auth — Auth0 | Full OAuth authentication with Auth0 and personalized coffee shop search. | — | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/auth-auth0) |
|
|
160
136
|
| <img src="docs/images/showcase-lumo.png" alt="Lumo Interactive AI Tutor" width="160" /> | Lumo — Interactive AI Tutor | Adaptive tutor with Mermaid diagrams, mind maps, quizzes, and fill-in-the-blank exercises. | [Try Demo](https://lumo-mcp-app-39519fdd.alpic.live/try) | [View code](https://github.com/connorads/lumo-mcp-app) |
|
|
161
137
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
We invite you to contribute and help improve Skybridge.
|
|
138
|
+
### Auth
|
|
165
139
|
|
|
166
|
-
|
|
140
|
+
| Preview | Provider | Description | Code |
|
|
141
|
+
| --- | --- | --- | --- |
|
|
142
|
+
| <img src="docs/images/showcase-clerk.png" alt="Auth Clerk" width="160" /> | Clerk | Full OAuth authentication with Clerk and personalized coffee shop search. | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/auth-clerk) |
|
|
143
|
+
| <img src="docs/images/showcase-workos.png" alt="Auth WorkOS AuthKit" width="160" /> | WorkOS AuthKit | Full OAuth authentication with WorkOS AuthKit and personalized coffee shop search. | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/auth-workos) |
|
|
144
|
+
| <img src="docs/images/showcase-stytch.png" alt="Auth Stytch" width="160" /> | Stytch | Full OAuth authentication with Stytch and personalized coffee shop search. | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/auth-stytch) |
|
|
145
|
+
| <img src="docs/images/showcase-auth0.png" alt="Auth Auth0" width="160" /> | Auth0 | Full OAuth authentication with Auth0 and personalized coffee shop search. | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/auth-auth0) |
|
|
167
146
|
|
|
168
|
-
|
|
169
|
-
- **Questions and Suggestions**: Need help building with Skybridge or have ideas to improve the framework, docs, examples, or developer experience? [Open an issue](https://github.com/alpic-ai/skybridge/issues) or share them on our [Discord](https://discord.com/invite/gNAazGueab).
|
|
170
|
-
- **Pull requests**: For code or documentation changes, please read the [Contributing Guide](https://github.com/alpic-ai/skybridge/blob/main/CONTRIBUTING.md) before opening a PR.
|
|
147
|
+
### UI and component libraries
|
|
171
148
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
Built and maintained by [Harijoe](https://github.com/harijoe), [Fred Barthelet](https://github.com/fredericbarthelet), and the [Alpic](https://alpic.ai) team.
|
|
177
|
-
|
|
178
|
-
<a href="https://github.com/alpic-ai/skybridge/graphs/contributors">
|
|
179
|
-
<img src="https://contrib.rocks/image?repo=alpic-ai/skybridge" alt="Skybridge contributors">
|
|
180
|
-
</a>
|
|
149
|
+
| Preview | App | Description | Demo | Code |
|
|
150
|
+
| --- | --- | --- | --- | --- |
|
|
151
|
+
| <img src="docs/images/showcase-manifest-ui.png" alt="Manifest UI" width="160" /> | Manifest UI | Agentic component library example for rich AI-powered experiences. | [Try Demo](https://manifest-ui.skybridge.tech/try) | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/manifest-ui) |
|
|
152
|
+
| <img src="docs/images/showcase-generative-ui.png" alt="Generative UI" width="160" /> | Generative UI | LLM-generated dynamic UIs with json-render and 36 pre-built shadcn/ui components. | [Try Demo](https://generative-ui.skybridge.tech/try) | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/generative-ui) |
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const ENTRY_WRAPPER_CONTENT = "import { __setBuildManifest } from \"skybridge/server\";\nimport manifest from \"./vite-manifest.js\";\n\n__setBuildManifest(manifest);\n\nconst userMod = await import(\"./server.js\");\nexport default userMod.default;\n";
|
|
2
|
+
export declare function emitEntryWrapper(distDir: string): void;
|
|
3
|
+
export declare function emitManifestModule(manifestPath: string, outPath: string): void;
|
|
4
|
+
export declare const VERCEL_FUNCTION_NAME = "mcp";
|
|
5
|
+
export declare const VERCEL_CONFIG: unknown;
|
|
6
|
+
export declare const VERCEL_VC_CONFIG: unknown;
|
|
7
|
+
export declare function emitVercelBuildOutput(root: string): Promise<void>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { cpSync, mkdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
// Primes the manifest in skybridge's module scope, then dynamically imports
|
|
4
|
+
// `./server.js` so user code runs *after* the side channel is set. The
|
|
5
|
+
// dynamic import is load-bearing: a static `export { default } from ...` is
|
|
6
|
+
// hoisted with the rest of the static graph and would evaluate `server.js`
|
|
7
|
+
// before `__setBuildManifest` runs.
|
|
8
|
+
export const ENTRY_WRAPPER_CONTENT = `import { __setBuildManifest } from "skybridge/server";
|
|
9
|
+
import manifest from "./vite-manifest.js";
|
|
10
|
+
|
|
11
|
+
__setBuildManifest(manifest);
|
|
12
|
+
|
|
13
|
+
const userMod = await import("./server.js");
|
|
14
|
+
export default userMod.default;
|
|
15
|
+
`;
|
|
16
|
+
export function emitEntryWrapper(distDir) {
|
|
17
|
+
writeFileSync(path.join(distDir, "__entry.js"), ENTRY_WRAPPER_CONTENT);
|
|
18
|
+
}
|
|
19
|
+
export function emitManifestModule(manifestPath, outPath) {
|
|
20
|
+
const manifest = readFileSync(manifestPath, "utf-8");
|
|
21
|
+
writeFileSync(outPath, `export default ${manifest};\n`);
|
|
22
|
+
}
|
|
23
|
+
export const VERCEL_FUNCTION_NAME = "mcp";
|
|
24
|
+
export const VERCEL_CONFIG = {
|
|
25
|
+
version: 3,
|
|
26
|
+
routes: [
|
|
27
|
+
{
|
|
28
|
+
src: "/assets/(.*)",
|
|
29
|
+
headers: { "Access-Control-Allow-Origin": "*" },
|
|
30
|
+
continue: true,
|
|
31
|
+
},
|
|
32
|
+
{ handle: "filesystem" },
|
|
33
|
+
{ src: "/(.*)", dest: `/${VERCEL_FUNCTION_NAME}` },
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
export const VERCEL_VC_CONFIG = {
|
|
37
|
+
runtime: "nodejs22.x",
|
|
38
|
+
handler: "index.js",
|
|
39
|
+
launcherType: "Nodejs",
|
|
40
|
+
shouldAddHelpers: true,
|
|
41
|
+
};
|
|
42
|
+
// Emit a Build Output API tree under `.vercel/output/`. Bundling the server
|
|
43
|
+
// with esbuild produces a self-contained function bundle, so we don't ship
|
|
44
|
+
// `node_modules` and don't touch tracked paths like `api/` or `public/`.
|
|
45
|
+
//
|
|
46
|
+
// Entry is `dist/__entry.js` — the wrapper that primes the Vite manifest via
|
|
47
|
+
// `__setBuildManifest` before importing user code. Bundling `dist/server.js`
|
|
48
|
+
// directly would skip that priming and 500 on view resource reads when the
|
|
49
|
+
// function falls back to `readFileSync('dist/assets/.vite/manifest.json')`
|
|
50
|
+
// (Vercel functions don't ship `dist/`).
|
|
51
|
+
export async function emitVercelBuildOutput(root) {
|
|
52
|
+
const outputDir = path.join(root, ".vercel", "output");
|
|
53
|
+
const funcDir = path.join(outputDir, "functions", `${VERCEL_FUNCTION_NAME}.func`);
|
|
54
|
+
const staticAssetsDir = path.join(outputDir, "static", "assets");
|
|
55
|
+
rmSync(outputDir, { recursive: true, force: true });
|
|
56
|
+
mkdirSync(funcDir, { recursive: true });
|
|
57
|
+
const { build } = await import("esbuild");
|
|
58
|
+
await build({
|
|
59
|
+
entryPoints: [path.join(root, "dist", "__entry.js")],
|
|
60
|
+
bundle: true,
|
|
61
|
+
platform: "node",
|
|
62
|
+
target: "node22",
|
|
63
|
+
format: "esm",
|
|
64
|
+
outfile: path.join(funcDir, "index.js"),
|
|
65
|
+
// Lets esbuild DCE dev-only branches that pull in vite/devtools.
|
|
66
|
+
define: { "process.env.NODE_ENV": '"production"' },
|
|
67
|
+
// Dev-only deps reachable from re-exports; safe to leave unresolved since
|
|
68
|
+
// the code paths that touch them are eliminated by the NODE_ENV define.
|
|
69
|
+
external: ["vite", "@skybridge/devtools"],
|
|
70
|
+
banner: {
|
|
71
|
+
// ESM bundles miss CJS interop globals that some deps reach for.
|
|
72
|
+
js: "import{createRequire}from'node:module';const require=createRequire(import.meta.url);",
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
writeFileSync(path.join(funcDir, ".vc-config.json"), `${JSON.stringify(VERCEL_VC_CONFIG, null, 2)}\n`);
|
|
76
|
+
writeFileSync(path.join(funcDir, "package.json"), `${JSON.stringify({ type: "module" }, null, 2)}\n`);
|
|
77
|
+
cpSync(path.join(root, "dist", "assets"), staticAssetsDir, {
|
|
78
|
+
recursive: true,
|
|
79
|
+
});
|
|
80
|
+
writeFileSync(path.join(outputDir, "config.json"), `${JSON.stringify(VERCEL_CONFIG, null, 2)}\n`);
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=build-helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-helpers.js","sourceRoot":"","sources":["../../src/cli/build-helpers.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n cpSync,\n mkdirSync,\n readFileSync,\n rmSync,\n writeFileSync,\n} from \"node:fs\";\nimport path from \"node:path\";\n\n// Primes the manifest in skybridge's module scope, then dynamically imports\n// `./server.js` so user code runs *after* the side channel is set. The\n// dynamic import is load-bearing: a static `export { default } from ...` is\n// hoisted with the rest of the static graph and would evaluate `server.js`\n// before `__setBuildManifest` runs.\nexport const ENTRY_WRAPPER_CONTENT = `import { __setBuildManifest } from \"skybridge/server\";\nimport manifest from \"./vite-manifest.js\";\n\n__setBuildManifest(manifest);\n\nconst userMod = await import(\"./server.js\");\nexport default userMod.default;\n`;\n\nexport function emitEntryWrapper(distDir: string): void {\n writeFileSync(path.join(distDir, \"__entry.js\"), ENTRY_WRAPPER_CONTENT);\n}\n\nexport function emitManifestModule(\n manifestPath: string,\n outPath: string,\n): void {\n const manifest = readFileSync(manifestPath, \"utf-8\");\n writeFileSync(outPath, `export default ${manifest};\\n`);\n}\n\nexport const VERCEL_FUNCTION_NAME = \"mcp\";\n\nexport const VERCEL_CONFIG: unknown = {\n version: 3,\n routes: [\n {\n src: \"/assets/(.*)\",\n headers: { \"Access-Control-Allow-Origin\": \"*\" },\n continue: true,\n },\n { handle: \"filesystem\" },\n { src: \"/(.*)\", dest: `/${VERCEL_FUNCTION_NAME}` },\n ],\n};\n\nexport const VERCEL_VC_CONFIG: unknown = {\n runtime: \"nodejs22.x\",\n handler: \"index.js\",\n launcherType: \"Nodejs\",\n shouldAddHelpers: true,\n};\n\n// Emit a Build Output API tree under `.vercel/output/`. Bundling the server\n// with esbuild produces a self-contained function bundle, so we don't ship\n// `node_modules` and don't touch tracked paths like `api/` or `public/`.\n//\n// Entry is `dist/__entry.js` — the wrapper that primes the Vite manifest via\n// `__setBuildManifest` before importing user code. Bundling `dist/server.js`\n// directly would skip that priming and 500 on view resource reads when the\n// function falls back to `readFileSync('dist/assets/.vite/manifest.json')`\n// (Vercel functions don't ship `dist/`).\nexport async function emitVercelBuildOutput(root: string): Promise<void> {\n const outputDir = path.join(root, \".vercel\", \"output\");\n const funcDir = path.join(\n outputDir,\n \"functions\",\n `${VERCEL_FUNCTION_NAME}.func`,\n );\n const staticAssetsDir = path.join(outputDir, \"static\", \"assets\");\n\n rmSync(outputDir, { recursive: true, force: true });\n mkdirSync(funcDir, { recursive: true });\n\n const { build } = await import(\"esbuild\");\n await build({\n entryPoints: [path.join(root, \"dist\", \"__entry.js\")],\n bundle: true,\n platform: \"node\",\n target: \"node22\",\n format: \"esm\",\n outfile: path.join(funcDir, \"index.js\"),\n // Lets esbuild DCE dev-only branches that pull in vite/devtools.\n define: { \"process.env.NODE_ENV\": '\"production\"' },\n // Dev-only deps reachable from re-exports; safe to leave unresolved since\n // the code paths that touch them are eliminated by the NODE_ENV define.\n external: [\"vite\", \"@skybridge/devtools\"],\n banner: {\n // ESM bundles miss CJS interop globals that some deps reach for.\n js: \"import{createRequire}from'node:module';const require=createRequire(import.meta.url);\",\n },\n });\n\n writeFileSync(\n path.join(funcDir, \".vc-config.json\"),\n `${JSON.stringify(VERCEL_VC_CONFIG, null, 2)}\\n`,\n );\n writeFileSync(\n path.join(funcDir, \"package.json\"),\n `${JSON.stringify({ type: \"module\" }, null, 2)}\\n`,\n );\n\n cpSync(path.join(root, \"dist\", \"assets\"), staticAssetsDir, {\n recursive: true,\n });\n\n writeFileSync(\n path.join(outputDir, \"config.json\"),\n `${JSON.stringify(VERCEL_CONFIG, null, 2)}\\n`,\n );\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
// esbuild's invariant check on TextEncoder/Uint8Array trips jsdom's polyfill.
|
|
3
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync, } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { describe, expect, it } from "vitest";
|
|
7
|
+
import { ENTRY_WRAPPER_CONTENT, emitEntryWrapper, emitManifestModule, emitVercelBuildOutput, VERCEL_CONFIG, VERCEL_VC_CONFIG, } from "./build-helpers.js";
|
|
8
|
+
function mkTmp(prefix = "skybridge-build-helpers-") {
|
|
9
|
+
return mkdtempSync(path.join(tmpdir(), prefix));
|
|
10
|
+
}
|
|
11
|
+
describe("emitEntryWrapper", () => {
|
|
12
|
+
it("writes dist/__entry.js that primes the manifest before importing user code", () => {
|
|
13
|
+
const dir = mkTmp();
|
|
14
|
+
emitEntryWrapper(dir);
|
|
15
|
+
const out = readFileSync(path.join(dir, "__entry.js"), "utf-8");
|
|
16
|
+
expect(out).toBe(ENTRY_WRAPPER_CONTENT);
|
|
17
|
+
expect(out).toContain('import { __setBuildManifest } from "skybridge/server"');
|
|
18
|
+
expect(out).toContain('import manifest from "./vite-manifest.js"');
|
|
19
|
+
expect(out).toContain("__setBuildManifest(manifest)");
|
|
20
|
+
// Dynamic import is load-bearing: `server.js` must evaluate after the
|
|
21
|
+
// setter runs, so a static re-export wouldn't work.
|
|
22
|
+
expect(out).toContain('await import("./server.js")');
|
|
23
|
+
expect(out).toContain("export default userMod.default");
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
describe("emitManifestModule", () => {
|
|
27
|
+
it("inlines the JSON manifest as an ESM default export", () => {
|
|
28
|
+
const dir = mkTmp();
|
|
29
|
+
const inPath = path.join(dir, "manifest.json");
|
|
30
|
+
const outPath = path.join(dir, "vite-manifest.js");
|
|
31
|
+
const manifest = { "src/views/foo.tsx": { file: "assets/foo-abc.js" } };
|
|
32
|
+
writeFileSync(inPath, JSON.stringify(manifest));
|
|
33
|
+
emitManifestModule(inPath, outPath);
|
|
34
|
+
const out = readFileSync(outPath, "utf-8");
|
|
35
|
+
expect(out.startsWith("export default ")).toBe(true);
|
|
36
|
+
const literal = out
|
|
37
|
+
.slice("export default ".length)
|
|
38
|
+
.trim()
|
|
39
|
+
.replace(/;$/, "");
|
|
40
|
+
expect(JSON.parse(literal)).toEqual(manifest);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe("emitVercelBuildOutput", () => {
|
|
44
|
+
it("emits a Build Output API tree with bundled function and static assets", async () => {
|
|
45
|
+
const root = mkTmp();
|
|
46
|
+
mkdirSync(path.join(root, "dist", "assets"), { recursive: true });
|
|
47
|
+
writeFileSync(path.join(root, "dist", "server.js"), "export default function handler(_req, res) { res.end('ok'); }\n");
|
|
48
|
+
// Minimal `dist/__entry.js` (the real wrapper imports `skybridge/server`,
|
|
49
|
+
// which isn't resolvable in this test's tmp dir — the function-bundling
|
|
50
|
+
// contract we care about here is just "bundle whatever `__entry.js`
|
|
51
|
+
// imports into a single function file").
|
|
52
|
+
writeFileSync(path.join(root, "dist", "__entry.js"), "const userMod = await import('./server.js');\nexport default userMod.default;\n");
|
|
53
|
+
writeFileSync(path.join(root, "dist", "assets", "view-abc.js"), "/* bundled view */\n");
|
|
54
|
+
await emitVercelBuildOutput(root);
|
|
55
|
+
const outputDir = path.join(root, ".vercel", "output");
|
|
56
|
+
const funcDir = path.join(outputDir, "functions", "mcp.func");
|
|
57
|
+
expect(existsSync(path.join(funcDir, "index.js"))).toBe(true);
|
|
58
|
+
expect(JSON.parse(readFileSync(path.join(funcDir, ".vc-config.json"), "utf-8"))).toEqual(VERCEL_VC_CONFIG);
|
|
59
|
+
expect(JSON.parse(readFileSync(path.join(funcDir, "package.json"), "utf-8"))).toEqual({ type: "module" });
|
|
60
|
+
expect(JSON.parse(readFileSync(path.join(outputDir, "config.json"), "utf-8"))).toEqual(VERCEL_CONFIG);
|
|
61
|
+
expect(readFileSync(path.join(outputDir, "static", "assets", "view-abc.js"), "utf-8")).toContain("bundled view");
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
//# sourceMappingURL=build-helpers.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-helpers.test.js","sourceRoot":"","sources":["../../src/cli/build-helpers.test.ts"],"names":[],"mappings":"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","sourcesContent":["// @vitest-environment node\n// esbuild's invariant check on TextEncoder/Uint8Array trips jsdom's polyfill.\nimport {\n existsSync,\n mkdirSync,\n mkdtempSync,\n readFileSync,\n writeFileSync,\n} from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport path from \"node:path\";\nimport { describe, expect, it } from \"vitest\";\nimport {\n ENTRY_WRAPPER_CONTENT,\n emitEntryWrapper,\n emitManifestModule,\n emitVercelBuildOutput,\n VERCEL_CONFIG,\n VERCEL_VC_CONFIG,\n} from \"./build-helpers.js\";\n\nfunction mkTmp(prefix = \"skybridge-build-helpers-\") {\n return mkdtempSync(path.join(tmpdir(), prefix));\n}\n\ndescribe(\"emitEntryWrapper\", () => {\n it(\"writes dist/__entry.js that primes the manifest before importing user code\", () => {\n const dir = mkTmp();\n emitEntryWrapper(dir);\n const out = readFileSync(path.join(dir, \"__entry.js\"), \"utf-8\");\n expect(out).toBe(ENTRY_WRAPPER_CONTENT);\n expect(out).toContain(\n 'import { __setBuildManifest } from \"skybridge/server\"',\n );\n expect(out).toContain('import manifest from \"./vite-manifest.js\"');\n expect(out).toContain(\"__setBuildManifest(manifest)\");\n // Dynamic import is load-bearing: `server.js` must evaluate after the\n // setter runs, so a static re-export wouldn't work.\n expect(out).toContain('await import(\"./server.js\")');\n expect(out).toContain(\"export default userMod.default\");\n });\n});\n\ndescribe(\"emitManifestModule\", () => {\n it(\"inlines the JSON manifest as an ESM default export\", () => {\n const dir = mkTmp();\n const inPath = path.join(dir, \"manifest.json\");\n const outPath = path.join(dir, \"vite-manifest.js\");\n const manifest = { \"src/views/foo.tsx\": { file: \"assets/foo-abc.js\" } };\n writeFileSync(inPath, JSON.stringify(manifest));\n emitManifestModule(inPath, outPath);\n const out = readFileSync(outPath, \"utf-8\");\n expect(out.startsWith(\"export default \")).toBe(true);\n const literal = out\n .slice(\"export default \".length)\n .trim()\n .replace(/;$/, \"\");\n expect(JSON.parse(literal)).toEqual(manifest);\n });\n});\n\ndescribe(\"emitVercelBuildOutput\", () => {\n it(\"emits a Build Output API tree with bundled function and static assets\", async () => {\n const root = mkTmp();\n mkdirSync(path.join(root, \"dist\", \"assets\"), { recursive: true });\n writeFileSync(\n path.join(root, \"dist\", \"server.js\"),\n \"export default function handler(_req, res) { res.end('ok'); }\\n\",\n );\n // Minimal `dist/__entry.js` (the real wrapper imports `skybridge/server`,\n // which isn't resolvable in this test's tmp dir — the function-bundling\n // contract we care about here is just \"bundle whatever `__entry.js`\n // imports into a single function file\").\n writeFileSync(\n path.join(root, \"dist\", \"__entry.js\"),\n \"const userMod = await import('./server.js');\\nexport default userMod.default;\\n\",\n );\n writeFileSync(\n path.join(root, \"dist\", \"assets\", \"view-abc.js\"),\n \"/* bundled view */\\n\",\n );\n\n await emitVercelBuildOutput(root);\n\n const outputDir = path.join(root, \".vercel\", \"output\");\n const funcDir = path.join(outputDir, \"functions\", \"mcp.func\");\n\n expect(existsSync(path.join(funcDir, \"index.js\"))).toBe(true);\n expect(\n JSON.parse(readFileSync(path.join(funcDir, \".vc-config.json\"), \"utf-8\")),\n ).toEqual(VERCEL_VC_CONFIG);\n expect(\n JSON.parse(readFileSync(path.join(funcDir, \"package.json\"), \"utf-8\")),\n ).toEqual({ type: \"module\" });\n expect(\n JSON.parse(readFileSync(path.join(outputDir, \"config.json\"), \"utf-8\")),\n ).toEqual(VERCEL_CONFIG);\n expect(\n readFileSync(\n path.join(outputDir, \"static\", \"assets\", \"view-abc.js\"),\n \"utf-8\",\n ),\n ).toContain(\"bundled view\");\n });\n});\n"]}
|
|
@@ -8,8 +8,8 @@ export declare function resolvePort(flagPort?: number): Promise<{
|
|
|
8
8
|
envWarning: string;
|
|
9
9
|
}>;
|
|
10
10
|
/**
|
|
11
|
-
* Returns the
|
|
12
|
-
*
|
|
11
|
+
* Returns the first available port at or after `startPort`, incrementing
|
|
12
|
+
* by one until a free port is found or `MAX_PORT_INCREMENT` is reached.
|
|
13
13
|
*
|
|
14
14
|
* @param host - Bind address for the check. Pass `"localhost"` for
|
|
15
15
|
* services that bind to 127.0.0.1 (e.g. Vite HMR). Omit for
|
package/dist/cli/detect-port.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import net from "node:net";
|
|
2
2
|
const DEFAULT_PORT = 3000;
|
|
3
|
+
const MAX_PORT_INCREMENT = 100;
|
|
3
4
|
export async function resolvePort(flagPort) {
|
|
4
5
|
if (flagPort && flagPort > 1) {
|
|
5
6
|
return { port: flagPort, fallback: false };
|
|
@@ -20,33 +21,21 @@ export async function resolvePort(flagPort) {
|
|
|
20
21
|
return { port, fallback: port !== DEFAULT_PORT };
|
|
21
22
|
}
|
|
22
23
|
/**
|
|
23
|
-
* Returns the
|
|
24
|
-
*
|
|
24
|
+
* Returns the first available port at or after `startPort`, incrementing
|
|
25
|
+
* by one until a free port is found or `MAX_PORT_INCREMENT` is reached.
|
|
25
26
|
*
|
|
26
27
|
* @param host - Bind address for the check. Pass `"localhost"` for
|
|
27
28
|
* services that bind to 127.0.0.1 (e.g. Vite HMR). Omit for
|
|
28
29
|
* services that bind to all interfaces (e.g. the HTTP server).
|
|
29
30
|
*/
|
|
30
31
|
export async function detectAvailablePort(startPort, host) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
for (let port = startPort; port < startPort + MAX_PORT_INCREMENT; port++) {
|
|
33
|
+
if (await isPortAvailable(port, host)) {
|
|
34
|
+
return port;
|
|
35
|
+
}
|
|
36
|
+
console.log(`Port ${port} is in use, trying another one...`);
|
|
34
37
|
}
|
|
35
|
-
|
|
36
|
-
const server = net.createServer();
|
|
37
|
-
server.once("error", reject);
|
|
38
|
-
server.once("listening", () => {
|
|
39
|
-
const addr = server.address();
|
|
40
|
-
if (addr && typeof addr === "object") {
|
|
41
|
-
const { port } = addr;
|
|
42
|
-
server.close(() => resolve(port));
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
server.close(() => reject(new Error("Failed to detect available port")));
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
server.listen(0, host);
|
|
49
|
-
});
|
|
38
|
+
throw new Error(`No available port found between ${startPort} and ${startPort + MAX_PORT_INCREMENT - 1}`);
|
|
50
39
|
}
|
|
51
40
|
function isPortAvailable(port, host) {
|
|
52
41
|
return new Promise((resolve) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detect-port.js","sourceRoot":"","sources":["../../src/cli/detect-port.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,UAAU,CAAC;AAE3B,MAAM,YAAY,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"detect-port.js","sourceRoot":"","sources":["../../src/cli/detect-port.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,UAAU,CAAC;AAE3B,MAAM,YAAY,GAAG,IAAI,CAAC;AAC1B,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAE/B,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,QAAiB;IACjD,IAAI,QAAQ,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;QAC7B,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC7C,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;IAChC,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;QAC9B,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;QAC3C,CAAC;QACD,OAAO;YACL,IAAI,EAAE,MAAM,mBAAmB,CAAC,YAAY,CAAC;YAC7C,QAAQ,EAAE,KAAK;YACf,UAAU,EAAE,iBAAiB,MAAM,+BAA+B;SACnE,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,mBAAmB,CAAC,YAAY,CAAC,CAAC;IACrD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,KAAK,YAAY,EAAE,CAAC;AACnD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,SAAiB,EACjB,IAAa;IAEb,KAAK,IAAI,IAAI,GAAG,SAAS,EAAE,IAAI,GAAG,SAAS,GAAG,kBAAkB,EAAE,IAAI,EAAE,EAAE,CAAC;QACzE,IAAI,MAAM,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;YACtC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,mCAAmC,CAAC,CAAC;IAC/D,CAAC;IACD,MAAM,IAAI,KAAK,CACb,mCAAmC,SAAS,QAAQ,SAAS,GAAG,kBAAkB,GAAG,CAAC,EAAE,CACzF,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,IAAY,EAAE,IAAa;IAClD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC;QAElC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3C,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE;YAC5B,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import net from \"node:net\";\n\nconst DEFAULT_PORT = 3000;\nconst MAX_PORT_INCREMENT = 100;\n\nexport async function resolvePort(flagPort?: number) {\n if (flagPort && flagPort > 1) {\n return { port: flagPort, fallback: false };\n }\n\n const rawEnv = process.env.PORT;\n if (rawEnv) {\n const parsed = Number(rawEnv);\n if (Number.isInteger(parsed) && parsed > 0) {\n return { port: parsed, fallback: false };\n }\n return {\n port: await detectAvailablePort(DEFAULT_PORT),\n fallback: false,\n envWarning: `Invalid PORT=\"${rawEnv}\", ignoring and using default`,\n };\n }\n\n const port = await detectAvailablePort(DEFAULT_PORT);\n return { port, fallback: port !== DEFAULT_PORT };\n}\n\n/**\n * Returns the first available port at or after `startPort`, incrementing\n * by one until a free port is found or `MAX_PORT_INCREMENT` is reached.\n *\n * @param host - Bind address for the check. Pass `\"localhost\"` for\n * services that bind to 127.0.0.1 (e.g. Vite HMR). Omit for\n * services that bind to all interfaces (e.g. the HTTP server).\n */\nexport async function detectAvailablePort(\n startPort: number,\n host?: string,\n): Promise<number> {\n for (let port = startPort; port < startPort + MAX_PORT_INCREMENT; port++) {\n if (await isPortAvailable(port, host)) {\n return port;\n }\n console.log(`Port ${port} is in use, trying another one...`);\n }\n throw new Error(\n `No available port found between ${startPort} and ${startPort + MAX_PORT_INCREMENT - 1}`,\n );\n}\n\nfunction isPortAvailable(port: number, host?: string): Promise<boolean> {\n return new Promise((resolve) => {\n const server = net.createServer();\n\n server.once(\"error\", () => resolve(false));\n server.once(\"listening\", () => {\n server.close(() => resolve(true));\n });\n\n server.listen(port, host);\n });\n}\n"]}
|
package/dist/cli/header.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"header.js","sourceRoot":"","sources":["../../src/cli/header.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAEhC,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,EACrB,OAAO,EACP,QAAQ,GAIT,EAAE,EAAE;IACH,OAAO,CACL,MAAC,GAAG,IAAC,YAAY,EAAE,CAAC,aAClB,MAAC,IAAI,IAAC,KAAK,EAAC,MAAM,EAAC,IAAI,6BACnB,IAAI,iBACD,EACP,MAAC,IAAI,IAAC,KAAK,EAAC,MAAM,mBAAI,OAAO,IAAQ,EACpC,QAAQ,IACL,CACP,CAAC;AACJ,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"header.js","sourceRoot":"","sources":["../../src/cli/header.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAEhC,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,EACrB,OAAO,EACP,QAAQ,GAIT,EAAE,EAAE;IACH,OAAO,CACL,MAAC,GAAG,IAAC,YAAY,EAAE,CAAC,aAClB,MAAC,IAAI,IAAC,KAAK,EAAC,MAAM,EAAC,IAAI,6BACnB,IAAI,iBACD,EACP,MAAC,IAAI,IAAC,KAAK,EAAC,MAAM,mBAAI,OAAO,IAAQ,EACpC,QAAQ,IACL,CACP,CAAC;AACJ,CAAC,CAAC","sourcesContent":["import { Box, Text } from \"ink\";\n\nexport const Header = ({\n version,\n children,\n}: {\n version: string;\n children?: React.ReactNode;\n}) => {\n return (\n <Box marginBottom={1}>\n <Text color=\"cyan\" bold>\n ⛰{\" \"}Skybridge\n </Text>\n <Text color=\"cyan\"> v{version}</Text>\n {children}\n </Box>\n );\n};\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolveViewsDir(root: string): Promise<string | undefined>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export async function resolveViewsDir(root) {
|
|
2
|
+
const { loadConfigFromFile } = await import("vite");
|
|
3
|
+
const loaded = await loadConfigFromFile({ command: "build", mode: "production" }, undefined, root);
|
|
4
|
+
const isPluginCandidate = (value) => typeof value === "object" && value !== null;
|
|
5
|
+
const plugins = [];
|
|
6
|
+
const walk = (value) => {
|
|
7
|
+
if (Array.isArray(value)) {
|
|
8
|
+
value.forEach(walk);
|
|
9
|
+
}
|
|
10
|
+
else if (isPluginCandidate(value)) {
|
|
11
|
+
plugins.push(value);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
walk(loaded?.config.plugins ?? []);
|
|
15
|
+
return plugins.find((p) => p.name === "skybridge")?.api?.viewsDir;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=resolve-views-dir.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-views-dir.js","sourceRoot":"","sources":["../../src/cli/resolve-views-dir.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,IAAY;IAEZ,MAAM,EAAE,kBAAkB,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC;IACpD,MAAM,MAAM,GAAG,MAAM,kBAAkB,CACrC,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,EACxC,SAAS,EACT,IAAI,CACL,CAAC;IAEF,MAAM,iBAAiB,GAAG,CACxB,KAAc,EAC2C,EAAE,CAC3D,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC;IAE9C,MAAM,OAAO,GAA0D,EAAE,CAAC;IAC1E,MAAM,IAAI,GAAG,CAAC,KAAc,EAAE,EAAE;QAC9B,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC;aAAM,IAAI,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;YACpC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACH,CAAC,CAAC;IACF,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;IACnC,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC;AACpE,CAAC","sourcesContent":["export async function resolveViewsDir(\n root: string,\n): Promise<string | undefined> {\n const { loadConfigFromFile } = await import(\"vite\");\n const loaded = await loadConfigFromFile(\n { command: \"build\", mode: \"production\" },\n undefined,\n root,\n );\n\n const isPluginCandidate = (\n value: unknown,\n ): value is { name?: string; api?: { viewsDir?: string } } =>\n typeof value === \"object\" && value !== null;\n\n const plugins: Array<{ name?: string; api?: { viewsDir?: string } }> = [];\n const walk = (value: unknown) => {\n if (Array.isArray(value)) {\n value.forEach(walk);\n } else if (isPluginCandidate(value)) {\n plugins.push(value);\n }\n };\n walk(loaded?.config.plugins ?? []);\n return plugins.find((p) => p.name === \"skybridge\")?.api?.viewsDir;\n}\n"]}
|