skybridge 1.0.2 → 1.0.4

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.
Files changed (136) hide show
  1. package/dist/cli/build-helpers.d.ts +7 -0
  2. package/dist/cli/build-helpers.js +82 -0
  3. package/dist/cli/build-helpers.js.map +1 -0
  4. package/dist/cli/build-helpers.test.d.ts +1 -0
  5. package/dist/cli/build-helpers.test.js +64 -0
  6. package/dist/cli/build-helpers.test.js.map +1 -0
  7. package/dist/cli/detect-port.d.ts +2 -2
  8. package/dist/cli/detect-port.js +9 -20
  9. package/dist/cli/detect-port.js.map +1 -1
  10. package/dist/cli/resolve-views-dir.d.ts +1 -0
  11. package/dist/cli/resolve-views-dir.js +17 -0
  12. package/dist/cli/resolve-views-dir.js.map +1 -0
  13. package/dist/cli/use-open-tunnel-browser.d.ts +6 -0
  14. package/dist/cli/use-open-tunnel-browser.js +19 -0
  15. package/dist/cli/use-open-tunnel-browser.js.map +1 -0
  16. package/dist/cli/use-typescript-check.js +1 -1
  17. package/dist/cli/use-typescript-check.js.map +1 -1
  18. package/dist/commands/build.d.ts +0 -1
  19. package/dist/commands/build.js +18 -30
  20. package/dist/commands/build.js.map +1 -1
  21. package/dist/commands/dev.js +19 -1
  22. package/dist/commands/dev.js.map +1 -1
  23. package/dist/commands/start.js +7 -1
  24. package/dist/commands/start.js.map +1 -1
  25. package/dist/server/build-manifest.test.d.ts +1 -0
  26. package/dist/server/build-manifest.test.js +27 -0
  27. package/dist/server/build-manifest.test.js.map +1 -0
  28. package/dist/server/content-helpers.d.ts +40 -0
  29. package/dist/server/content-helpers.js +33 -0
  30. package/dist/server/content-helpers.js.map +1 -1
  31. package/dist/server/express.test.js +30 -0
  32. package/dist/server/express.test.js.map +1 -1
  33. package/dist/server/file-ref.d.ts +20 -0
  34. package/dist/server/file-ref.js +19 -0
  35. package/dist/server/file-ref.js.map +1 -1
  36. package/dist/server/index.d.ts +1 -1
  37. package/dist/server/index.js +1 -1
  38. package/dist/server/index.js.map +1 -1
  39. package/dist/server/middleware.d.ts +16 -3
  40. package/dist/server/middleware.js.map +1 -1
  41. package/dist/server/server.d.ts +136 -1
  42. package/dist/server/server.js +181 -57
  43. package/dist/server/server.js.map +1 -1
  44. package/dist/test/view.test.js +45 -0
  45. package/dist/test/view.test.js.map +1 -1
  46. package/dist/web/bridges/apps-sdk/adaptor.d.ts +2 -0
  47. package/dist/web/bridges/apps-sdk/adaptor.js +5 -0
  48. package/dist/web/bridges/apps-sdk/adaptor.js.map +1 -1
  49. package/dist/web/bridges/apps-sdk/bridge.d.ts +1 -0
  50. package/dist/web/bridges/apps-sdk/bridge.js +1 -0
  51. package/dist/web/bridges/apps-sdk/bridge.js.map +1 -1
  52. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.d.ts +11 -0
  53. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js +11 -0
  54. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js.map +1 -1
  55. package/dist/web/bridges/get-adaptor.d.ts +7 -0
  56. package/dist/web/bridges/get-adaptor.js +7 -0
  57. package/dist/web/bridges/get-adaptor.js.map +1 -1
  58. package/dist/web/bridges/mcp-app/adaptor.d.ts +3 -1
  59. package/dist/web/bridges/mcp-app/adaptor.js +4 -0
  60. package/dist/web/bridges/mcp-app/adaptor.js.map +1 -1
  61. package/dist/web/bridges/mcp-app/bridge.d.ts +4 -2
  62. package/dist/web/bridges/mcp-app/bridge.js +23 -1
  63. package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
  64. package/dist/web/bridges/mcp-app/use-mcp-app-context.d.ts +12 -0
  65. package/dist/web/bridges/mcp-app/use-mcp-app-context.js +12 -0
  66. package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -1
  67. package/dist/web/bridges/mcp-app/view-tools.test.d.ts +1 -0
  68. package/dist/web/bridges/mcp-app/view-tools.test.js +144 -0
  69. package/dist/web/bridges/mcp-app/view-tools.test.js.map +1 -0
  70. package/dist/web/bridges/types.d.ts +81 -1
  71. package/dist/web/bridges/types.js.map +1 -1
  72. package/dist/web/bridges/use-host-context.d.ts +5 -0
  73. package/dist/web/bridges/use-host-context.js +5 -0
  74. package/dist/web/bridges/use-host-context.js.map +1 -1
  75. package/dist/web/create-store.d.ts +26 -0
  76. package/dist/web/create-store.js +26 -0
  77. package/dist/web/create-store.js.map +1 -1
  78. package/dist/web/data-llm.d.ts +33 -0
  79. package/dist/web/data-llm.js +28 -0
  80. package/dist/web/data-llm.js.map +1 -1
  81. package/dist/web/generate-helpers.d.ts +2 -0
  82. package/dist/web/generate-helpers.js +2 -0
  83. package/dist/web/generate-helpers.js.map +1 -1
  84. package/dist/web/hooks/index.d.ts +1 -0
  85. package/dist/web/hooks/index.js +1 -0
  86. package/dist/web/hooks/index.js.map +1 -1
  87. package/dist/web/hooks/use-call-tool.d.ts +45 -0
  88. package/dist/web/hooks/use-call-tool.js +28 -0
  89. package/dist/web/hooks/use-call-tool.js.map +1 -1
  90. package/dist/web/hooks/use-display-mode.d.ts +20 -0
  91. package/dist/web/hooks/use-display-mode.js +20 -0
  92. package/dist/web/hooks/use-display-mode.js.map +1 -1
  93. package/dist/web/hooks/use-files.d.ts +32 -0
  94. package/dist/web/hooks/use-files.js +32 -0
  95. package/dist/web/hooks/use-files.js.map +1 -1
  96. package/dist/web/hooks/use-layout.d.ts +2 -0
  97. package/dist/web/hooks/use-layout.js +2 -0
  98. package/dist/web/hooks/use-layout.js.map +1 -1
  99. package/dist/web/hooks/use-open-external.d.ts +17 -0
  100. package/dist/web/hooks/use-open-external.js +16 -0
  101. package/dist/web/hooks/use-open-external.js.map +1 -1
  102. package/dist/web/hooks/use-register-view-tool.d.ts +38 -0
  103. package/dist/web/hooks/use-register-view-tool.js +50 -0
  104. package/dist/web/hooks/use-register-view-tool.js.map +1 -0
  105. package/dist/web/hooks/use-request-close.d.ts +14 -0
  106. package/dist/web/hooks/use-request-close.js +13 -0
  107. package/dist/web/hooks/use-request-close.js.map +1 -1
  108. package/dist/web/hooks/use-request-modal.d.ts +16 -1
  109. package/dist/web/hooks/use-request-modal.js +16 -1
  110. package/dist/web/hooks/use-request-modal.js.map +1 -1
  111. package/dist/web/hooks/use-request-size.d.ts +17 -0
  112. package/dist/web/hooks/use-request-size.js +16 -0
  113. package/dist/web/hooks/use-request-size.js.map +1 -1
  114. package/dist/web/hooks/use-send-follow-up-message.d.ts +17 -0
  115. package/dist/web/hooks/use-send-follow-up-message.js +17 -0
  116. package/dist/web/hooks/use-send-follow-up-message.js.map +1 -1
  117. package/dist/web/hooks/use-set-open-in-app-url.d.ts +17 -0
  118. package/dist/web/hooks/use-set-open-in-app-url.js +17 -0
  119. package/dist/web/hooks/use-set-open-in-app-url.js.map +1 -1
  120. package/dist/web/hooks/use-tool-info.d.ts +33 -0
  121. package/dist/web/hooks/use-tool-info.js +26 -0
  122. package/dist/web/hooks/use-tool-info.js.map +1 -1
  123. package/dist/web/hooks/use-user.d.ts +2 -0
  124. package/dist/web/hooks/use-user.js +2 -0
  125. package/dist/web/hooks/use-user.js.map +1 -1
  126. package/dist/web/hooks/use-view-state.d.ts +21 -0
  127. package/dist/web/hooks/use-view-state.js.map +1 -1
  128. package/dist/web/mount-view.d.ts +19 -0
  129. package/dist/web/mount-view.js +19 -0
  130. package/dist/web/mount-view.js.map +1 -1
  131. package/dist/web/plugin/plugin.d.ts +28 -0
  132. package/dist/web/plugin/plugin.js +26 -0
  133. package/dist/web/plugin/plugin.js.map +1 -1
  134. package/dist/web/types.d.ts +4 -0
  135. package/dist/web/types.js.map +1 -1
  136. package/package.json +4 -2
@@ -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 given port if available, otherwise lets the OS
12
- * pick a free port via `listen(0)`.
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
@@ -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 given port if available, otherwise lets the OS
24
- * pick a free port via `listen(0)`.
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
- const available = await isPortAvailable(startPort, host);
32
- if (available) {
33
- return startPort;
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
- return new Promise((resolve, reject) => {
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;AAE1B,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,MAAM,SAAS,GAAG,MAAM,eAAe,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IACzD,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,MAAM,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC;QAElC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC7B,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE;YAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;YAC9B,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACrC,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC;gBACtB,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;YACpC,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAChB,MAAM,CAAC,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC,CACrD,CAAC;YACJ,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;AACL,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;\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 given port if available, otherwise lets the OS\n * pick a free port via `listen(0)`.\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 const available = await isPortAvailable(startPort, host);\n if (available) {\n return startPort;\n }\n\n return new Promise((resolve, reject) => {\n const server = net.createServer();\n\n server.once(\"error\", reject);\n server.once(\"listening\", () => {\n const addr = server.address();\n if (addr && typeof addr === \"object\") {\n const { port } = addr;\n server.close(() => resolve(port));\n } else {\n server.close(() =>\n reject(new Error(\"Failed to detect available port\")),\n );\n }\n });\n\n server.listen(0, host);\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"]}
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"]}
@@ -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"]}
@@ -0,0 +1,6 @@
1
+ import type { TunnelState } from "./use-tunnel.js";
2
+ /**
3
+ * Opens the tunnel URL in the browser once the tunnel reaches "connected"
4
+ * state. Fires at most once per mount.
5
+ */
6
+ export declare function useOpenTunnelBrowser(tunnelState: TunnelState, enabled: boolean): void;
@@ -0,0 +1,19 @@
1
+ import open from "open";
2
+ import { useEffect, useRef } from "react";
3
+ /**
4
+ * Opens the tunnel URL in the browser once the tunnel reaches "connected"
5
+ * state. Fires at most once per mount.
6
+ */
7
+ export function useOpenTunnelBrowser(tunnelState, enabled) {
8
+ const opened = useRef(false);
9
+ useEffect(() => {
10
+ if (!enabled || opened.current) {
11
+ return;
12
+ }
13
+ if (tunnelState.status === "connected") {
14
+ opened.current = true;
15
+ void open(tunnelState.url).catch(() => { });
16
+ }
17
+ }, [tunnelState, enabled]);
18
+ }
19
+ //# sourceMappingURL=use-open-tunnel-browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-open-tunnel-browser.js","sourceRoot":"","sources":["../../src/cli/use-open-tunnel-browser.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAG1C;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAClC,WAAwB,EACxB,OAAgB;IAEhB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAE7B,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YAC/B,OAAO;QACT,CAAC;QACD,IAAI,WAAW,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;YACvC,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;YACtB,KAAK,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC,EAAE,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;AAC7B,CAAC","sourcesContent":["import open from \"open\";\nimport { useEffect, useRef } from \"react\";\nimport type { TunnelState } from \"./use-tunnel.js\";\n\n/**\n * Opens the tunnel URL in the browser once the tunnel reaches \"connected\"\n * state. Fires at most once per mount.\n */\nexport function useOpenTunnelBrowser(\n tunnelState: TunnelState,\n enabled: boolean,\n): void {\n const opened = useRef(false);\n\n useEffect(() => {\n if (!enabled || opened.current) {\n return;\n }\n if (tunnelState.status === \"connected\") {\n opened.current = true;\n void open(tunnelState.url).catch(() => {});\n }\n }, [tunnelState, enabled]);\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { spawn } from "node:child_process";
2
1
  import { isAbsolute, relative } from "node:path";
2
+ import spawn from "cross-spawn";
3
3
  import { useEffect, useRef, useState } from "react";
4
4
  // TypeScript nests from general to specific — the deepest line is the root cause.
5
5
  function extractBestMessage(message, continuationLines) {
@@ -1 +1 @@
1
- {"version":3,"file":"use-typescript-check.js","sourceRoot":"","sources":["../../src/cli/use-typescript-check.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { isAbsolute, relative } from \"node:path\";\nimport { useEffect, useRef, useState } from \"react\";\n\ntype TsError = {\n file: string;\n line: number;\n col: number;\n code: string;\n message: string;\n};\n\n// TypeScript nests from general to specific — the deepest line is the root cause.\nfunction extractBestMessage(\n message: string,\n continuationLines: Array<string>,\n): string {\n if (!continuationLines.length) {\n return message;\n }\n let maxIndent = 0;\n for (const line of continuationLines) {\n const indent = line.length - line.trimStart().length;\n if (indent > maxIndent) {\n maxIndent = indent;\n }\n }\n const deepest = continuationLines\n .filter((l) => l.length - l.trimStart().length === maxIndent)\n .map((l) => l.trim())\n .filter(Boolean)[0];\n return deepest ?? message;\n}\n\nexport function useTypeScriptCheck(): Array<TsError> {\n const tsProcessRef = useRef<ReturnType<typeof spawn> | null>(null);\n const [tsErrors, setTsErrors] = useState<Array<TsError>>([]);\n\n useEffect(() => {\n const tsProcess = spawn(\n \"npx\",\n [\"tsc\", \"--noEmit\", \"--watch\", \"--pretty\", \"false\"],\n {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n shell: true,\n },\n );\n\n tsProcessRef.current = tsProcess;\n\n let outputBuffer = \"\";\n let currentErrors: Array<TsError> = [];\n let pendingError: TsError | null = null;\n let continuationLines: Array<string> = [];\n\n const flushPending = () => {\n if (!pendingError) {\n return;\n }\n pendingError.message = extractBestMessage(\n pendingError.message,\n continuationLines,\n );\n currentErrors.push(pendingError);\n pendingError = null;\n continuationLines = [];\n };\n\n const processOutput = (data: Buffer) => {\n outputBuffer += data.toString();\n const lines = outputBuffer.split(\"\\n\");\n outputBuffer = lines.pop() || \"\";\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n const errorMatch = trimmed.match(\n /^(.+?)\\((\\d+),(\\d+)\\):\\s+error\\s+(TS\\d+)?\\s*:?\\s*(.+)$/,\n );\n if (errorMatch) {\n flushPending();\n const [, file, lineStr, colStr, code, message] = errorMatch;\n if (file && lineStr && colStr && message) {\n let cleanFile = file.trim();\n if (isAbsolute(cleanFile)) {\n cleanFile = relative(process.cwd(), cleanFile);\n }\n pendingError = {\n file: cleanFile,\n line: Number.parseInt(lineStr, 10),\n col: Number.parseInt(colStr, 10),\n code: code ?? \"\",\n message: message.trim(),\n };\n }\n continue;\n }\n\n if (pendingError && line.startsWith(\" \")) {\n continuationLines.push(line);\n continue;\n }\n\n if (trimmed.includes(\"Found\") && trimmed.includes(\"error\")) {\n flushPending();\n setTsErrors(trimmed.match(/Found 0 error/) ? [] : [...currentErrors]);\n currentErrors = [];\n }\n }\n };\n\n if (tsProcess.stdout) {\n tsProcess.stdout.on(\"data\", processOutput);\n }\n if (tsProcess.stderr) {\n tsProcess.stderr.on(\"data\", processOutput);\n }\n\n return () => {\n if (tsProcessRef.current) {\n tsProcessRef.current.kill();\n }\n };\n }, []);\n\n return tsErrors;\n}\n"]}
1
+ {"version":3,"file":"use-typescript-check.js","sourceRoot":"","sources":["../../src/cli/use-typescript-check.ts"],"names":[],"mappings":"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","sourcesContent":["import { isAbsolute, relative } from \"node:path\";\nimport spawn from \"cross-spawn\";\nimport { useEffect, useRef, useState } from \"react\";\n\ntype TsError = {\n file: string;\n line: number;\n col: number;\n code: string;\n message: string;\n};\n\n// TypeScript nests from general to specific — the deepest line is the root cause.\nfunction extractBestMessage(\n message: string,\n continuationLines: Array<string>,\n): string {\n if (!continuationLines.length) {\n return message;\n }\n let maxIndent = 0;\n for (const line of continuationLines) {\n const indent = line.length - line.trimStart().length;\n if (indent > maxIndent) {\n maxIndent = indent;\n }\n }\n const deepest = continuationLines\n .filter((l) => l.length - l.trimStart().length === maxIndent)\n .map((l) => l.trim())\n .filter(Boolean)[0];\n return deepest ?? message;\n}\n\nexport function useTypeScriptCheck(): Array<TsError> {\n const tsProcessRef = useRef<ReturnType<typeof spawn> | null>(null);\n const [tsErrors, setTsErrors] = useState<Array<TsError>>([]);\n\n useEffect(() => {\n const tsProcess = spawn(\n \"npx\",\n [\"tsc\", \"--noEmit\", \"--watch\", \"--pretty\", \"false\"],\n {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n shell: true,\n },\n );\n\n tsProcessRef.current = tsProcess;\n\n let outputBuffer = \"\";\n let currentErrors: Array<TsError> = [];\n let pendingError: TsError | null = null;\n let continuationLines: Array<string> = [];\n\n const flushPending = () => {\n if (!pendingError) {\n return;\n }\n pendingError.message = extractBestMessage(\n pendingError.message,\n continuationLines,\n );\n currentErrors.push(pendingError);\n pendingError = null;\n continuationLines = [];\n };\n\n const processOutput = (data: Buffer) => {\n outputBuffer += data.toString();\n const lines = outputBuffer.split(\"\\n\");\n outputBuffer = lines.pop() || \"\";\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n const errorMatch = trimmed.match(\n /^(.+?)\\((\\d+),(\\d+)\\):\\s+error\\s+(TS\\d+)?\\s*:?\\s*(.+)$/,\n );\n if (errorMatch) {\n flushPending();\n const [, file, lineStr, colStr, code, message] = errorMatch;\n if (file && lineStr && colStr && message) {\n let cleanFile = file.trim();\n if (isAbsolute(cleanFile)) {\n cleanFile = relative(process.cwd(), cleanFile);\n }\n pendingError = {\n file: cleanFile,\n line: Number.parseInt(lineStr, 10),\n col: Number.parseInt(colStr, 10),\n code: code ?? \"\",\n message: message.trim(),\n };\n }\n continue;\n }\n\n if (pendingError && line.startsWith(\" \")) {\n continuationLines.push(line);\n continue;\n }\n\n if (trimmed.includes(\"Found\") && trimmed.includes(\"error\")) {\n flushPending();\n setTsErrors(trimmed.match(/Found 0 error/) ? [] : [...currentErrors]);\n currentErrors = [];\n }\n }\n };\n\n if (tsProcess.stdout) {\n tsProcess.stdout.on(\"data\", processOutput);\n }\n if (tsProcess.stderr) {\n tsProcess.stderr.on(\"data\", processOutput);\n }\n\n return () => {\n if (tsProcessRef.current) {\n tsProcessRef.current.kill();\n }\n };\n }, []);\n\n return tsErrors;\n}\n"]}
@@ -4,6 +4,5 @@ export declare const commandSteps: CommandStep[];
4
4
  export default class Build extends Command {
5
5
  static description: string;
6
6
  static examples: string[];
7
- static flags: {};
8
7
  run(): Promise<void>;
9
8
  }
@@ -1,28 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { rmSync, writeFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { Command } from "@oclif/core";
5
5
  import { Box, render, Text } from "ink";
6
6
  import { useEffect } from "react";
7
+ import { emitEntryWrapper, emitManifestModule, emitVercelBuildOutput, } from "../cli/build-helpers.js";
7
8
  import { Header } from "../cli/header.js";
9
+ import { resolveViewsDir } from "../cli/resolve-views-dir.js";
8
10
  import { useExecuteSteps } from "../cli/use-execute-steps.js";
9
11
  import { scanAndWriteViewsDts } from "../web/plugin/scan-views.js";
10
- async function resolveViewsDir(root) {
11
- const { loadConfigFromFile } = await import("vite");
12
- const loaded = await loadConfigFromFile({ command: "build", mode: "production" }, undefined, root);
13
- const isPluginCandidate = (value) => typeof value === "object" && value !== null;
14
- const plugins = [];
15
- const walk = (value) => {
16
- if (Array.isArray(value)) {
17
- value.forEach(walk);
18
- }
19
- else if (isPluginCandidate(value)) {
20
- plugins.push(value);
21
- }
22
- };
23
- walk(loaded?.config.plugins ?? []);
24
- return plugins.find((p) => p.name === "skybridge")?.api?.viewsDir;
25
- }
26
12
  export const commandSteps = [
27
13
  {
28
14
  label: "Scanning views",
@@ -43,23 +29,25 @@ export const commandSteps = [
43
29
  },
44
30
  {
45
31
  label: "Emitting manifest module",
46
- // Inline the Vite manifest as a JS module so the server can `import` it
32
+ // Inline the Vite manifest as a JS module so the wrapper can `import` it
47
33
  // instead of `readFileSync(process.cwd() + ...)` at runtime — required for
48
34
  // workerd, where neither cwd nor the assets directory is readable.
49
- // The path mirrors `skybridge start`'s entry convention (dist/server.js)
50
- // so the import in the user's entry resolves to a sibling file.
51
35
  run: () => {
52
36
  const root = process.cwd();
53
- const manifest = readFileSync(path.join(root, "dist", "assets", ".vite", "manifest.json"), "utf-8");
54
- writeFileSync(path.join(root, "dist", "vite-manifest.js"), `export default ${manifest};\n`);
37
+ emitManifestModule(path.join(root, "dist", "assets", ".vite", "manifest.json"), path.join(root, "dist", "vite-manifest.js"));
38
+ },
39
+ },
40
+ {
41
+ label: "Emitting entry wrapper",
42
+ // dist/__entry.js primes the Vite manifest via __setBuildManifest, then
43
+ // dynamically imports user code. Deploy targets (Cloudflare, Vercel)
44
+ // bundle from here so the manifest is available at runtime.
45
+ run: () => {
46
+ emitEntryWrapper(path.join(process.cwd(), "dist"));
55
47
  },
56
48
  },
57
49
  {
58
50
  label: "Emitting Cloudflare redirects",
59
- // Cloudflare's `assets.directory` maps URL → file literally — no
60
- // mount-strip like `app.use("/assets", express.static(...))`. Rewrite
61
- // `/assets/assets/*` to `/assets/*` before lookup; status 200 =
62
- // server-side rewrite, not HTTP redirect.
63
51
  run: () => {
64
52
  const root = process.cwd();
65
53
  writeFileSync(path.join(root, "dist", "assets", "_redirects"), "/assets/assets/* /assets/:splat 200\n");
@@ -67,19 +55,19 @@ export const commandSteps = [
67
55
  },
68
56
  {
69
57
  label: "Emitting Cloudflare headers",
70
- // Cloudflare's static asset handler bypasses the worker entirely, so
71
- // `app.use("/assets", cors())` never fires for asset requests. Attach
72
- // CORS at the edge so cross-origin view iframes can load JS/CSS.
73
58
  run: () => {
74
59
  const root = process.cwd();
75
60
  writeFileSync(path.join(root, "dist", "assets", "_headers"), "/assets/*\n Access-Control-Allow-Origin: *\n");
76
61
  },
77
62
  },
63
+ {
64
+ label: "Emitting Vercel build output",
65
+ run: () => emitVercelBuildOutput(process.cwd()),
66
+ },
78
67
  ];
79
68
  export default class Build extends Command {
80
69
  static description = "Build the views and MCP server";
81
70
  static examples = ["skybridge build"];
82
- static flags = {};
83
71
  async run() {
84
72
  const App = () => {
85
73
  const { currentStep, status, error, execute } = useExecuteSteps(commandSteps);
@@ -1 +1 @@
1
- {"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/commands/build.tsx"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { Command } from \"@oclif/core\";\nimport { Box, render, Text } from \"ink\";\nimport { useEffect } from \"react\";\nimport { Header } from \"../cli/header.js\";\nimport { type CommandStep, useExecuteSteps } from \"../cli/use-execute-steps.js\";\nimport { scanAndWriteViewsDts } from \"../web/plugin/scan-views.js\";\n\nasync function resolveViewsDir(root: string): 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\nexport const commandSteps: CommandStep[] = [\n {\n label: \"Scanning views\",\n run: async () => {\n const root = process.cwd();\n const viewsDir = await resolveViewsDir(root);\n scanAndWriteViewsDts(root, viewsDir);\n },\n },\n {\n label: \"Compiling server\",\n run: () => rmSync(\"dist\", { recursive: true, force: true }),\n command: \"tsc -b --force\",\n },\n {\n label: \"Building views\",\n command: \"vite build\",\n },\n {\n label: \"Emitting manifest module\",\n // Inline the Vite manifest as a JS module so the server can `import` it\n // instead of `readFileSync(process.cwd() + ...)` at runtime — required for\n // workerd, where neither cwd nor the assets directory is readable.\n // The path mirrors `skybridge start`'s entry convention (dist/server.js)\n // so the import in the user's entry resolves to a sibling file.\n run: () => {\n const root = process.cwd();\n const manifest = readFileSync(\n path.join(root, \"dist\", \"assets\", \".vite\", \"manifest.json\"),\n \"utf-8\",\n );\n writeFileSync(\n path.join(root, \"dist\", \"vite-manifest.js\"),\n `export default ${manifest};\\n`,\n );\n },\n },\n\n {\n label: \"Emitting Cloudflare redirects\",\n // Cloudflare's `assets.directory` maps URL → file literally — no\n // mount-strip like `app.use(\"/assets\", express.static(...))`. Rewrite\n // `/assets/assets/*` to `/assets/*` before lookup; status 200 =\n // server-side rewrite, not HTTP redirect.\n run: () => {\n const root = process.cwd();\n writeFileSync(\n path.join(root, \"dist\", \"assets\", \"_redirects\"),\n \"/assets/assets/* /assets/:splat 200\\n\",\n );\n },\n },\n {\n label: \"Emitting Cloudflare headers\",\n // Cloudflare's static asset handler bypasses the worker entirely, so\n // `app.use(\"/assets\", cors())` never fires for asset requests. Attach\n // CORS at the edge so cross-origin view iframes can load JS/CSS.\n run: () => {\n const root = process.cwd();\n writeFileSync(\n path.join(root, \"dist\", \"assets\", \"_headers\"),\n \"/assets/*\\n Access-Control-Allow-Origin: *\\n\",\n );\n },\n },\n];\n\nexport default class Build extends Command {\n static override description = \"Build the views and MCP server\";\n static override examples = [\"skybridge build\"];\n static override flags = {};\n\n public async run(): Promise<void> {\n const App = () => {\n const { currentStep, status, error, execute } =\n useExecuteSteps(commandSteps);\n\n useEffect(() => {\n execute();\n }, [execute]);\n\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Header version={this.config.version}>\n <Text color=\"green\"> → building for production…</Text>\n </Header>\n\n {commandSteps.map((step, index) => {\n const isCurrent = index === currentStep && status === \"running\";\n const isCompleted = index < currentStep || status === \"success\";\n const isError = status === \"error\" && index === currentStep;\n\n return (\n <Box key={step.label} marginBottom={0}>\n <Text color={isError ? \"red\" : isCompleted ? \"green\" : \"grey\"}>\n {isError ? \"✗\" : isCompleted ? \"✓\" : isCurrent ? \"⟳\" : \"○\"}{\" \"}\n {step.label}\n </Text>\n </Box>\n );\n })}\n\n {status === \"success\" && (\n <Box marginTop={1}>\n <Text color=\"green\" bold>\n ✓ Build completed successfully!\n </Text>\n </Box>\n )}\n\n {status === \"error\" && error && (\n <Box marginTop={1} flexDirection=\"column\">\n <Text color=\"red\" bold>\n ✗ Build failed\n </Text>\n <Box marginTop={1} flexDirection=\"column\">\n {error.split(\"\\n\").map((line) => (\n <Text key={line} color=\"red\">\n {line}\n </Text>\n ))}\n </Box>\n </Box>\n )}\n </Box>\n );\n };\n\n render(<App />, {\n exitOnCtrlC: true,\n patchConsole: false,\n });\n }\n}\n"]}
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/commands/build.tsx"],"names":[],"mappings":"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","sourcesContent":["import { rmSync, writeFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { Command } from \"@oclif/core\";\nimport { Box, render, Text } from \"ink\";\nimport { useEffect } from \"react\";\nimport {\n emitEntryWrapper,\n emitManifestModule,\n emitVercelBuildOutput,\n} from \"../cli/build-helpers.js\";\nimport { Header } from \"../cli/header.js\";\nimport { resolveViewsDir } from \"../cli/resolve-views-dir.js\";\nimport { type CommandStep, useExecuteSteps } from \"../cli/use-execute-steps.js\";\nimport { scanAndWriteViewsDts } from \"../web/plugin/scan-views.js\";\n\nexport const commandSteps: CommandStep[] = [\n {\n label: \"Scanning views\",\n run: async () => {\n const root = process.cwd();\n const viewsDir = await resolveViewsDir(root);\n scanAndWriteViewsDts(root, viewsDir);\n },\n },\n {\n label: \"Compiling server\",\n run: () => rmSync(\"dist\", { recursive: true, force: true }),\n command: \"tsc -b --force\",\n },\n {\n label: \"Building views\",\n command: \"vite build\",\n },\n {\n label: \"Emitting manifest module\",\n // Inline the Vite manifest as a JS module so the wrapper can `import` it\n // instead of `readFileSync(process.cwd() + ...)` at runtime — required for\n // workerd, where neither cwd nor the assets directory is readable.\n run: () => {\n const root = process.cwd();\n emitManifestModule(\n path.join(root, \"dist\", \"assets\", \".vite\", \"manifest.json\"),\n path.join(root, \"dist\", \"vite-manifest.js\"),\n );\n },\n },\n {\n label: \"Emitting entry wrapper\",\n // dist/__entry.js primes the Vite manifest via __setBuildManifest, then\n // dynamically imports user code. Deploy targets (Cloudflare, Vercel)\n // bundle from here so the manifest is available at runtime.\n run: () => {\n emitEntryWrapper(path.join(process.cwd(), \"dist\"));\n },\n },\n {\n label: \"Emitting Cloudflare redirects\",\n run: () => {\n const root = process.cwd();\n writeFileSync(\n path.join(root, \"dist\", \"assets\", \"_redirects\"),\n \"/assets/assets/* /assets/:splat 200\\n\",\n );\n },\n },\n {\n label: \"Emitting Cloudflare headers\",\n run: () => {\n const root = process.cwd();\n writeFileSync(\n path.join(root, \"dist\", \"assets\", \"_headers\"),\n \"/assets/*\\n Access-Control-Allow-Origin: *\\n\",\n );\n },\n },\n {\n label: \"Emitting Vercel build output\",\n run: () => emitVercelBuildOutput(process.cwd()),\n },\n];\n\nexport default class Build extends Command {\n static override description = \"Build the views and MCP server\";\n static override examples = [\"skybridge build\"];\n\n public async run(): Promise<void> {\n const App = () => {\n const { currentStep, status, error, execute } =\n useExecuteSteps(commandSteps);\n\n useEffect(() => {\n execute();\n }, [execute]);\n\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Header version={this.config.version}>\n <Text color=\"green\"> → building for production…</Text>\n </Header>\n\n {commandSteps.map((step, index) => {\n const isCurrent = index === currentStep && status === \"running\";\n const isCompleted = index < currentStep || status === \"success\";\n const isError = status === \"error\" && index === currentStep;\n\n return (\n <Box key={step.label} marginBottom={0}>\n <Text color={isError ? \"red\" : isCompleted ? \"green\" : \"grey\"}>\n {isError ? \"✗\" : isCompleted ? \"✓\" : isCurrent ? \"⟳\" : \"○\"}{\" \"}\n {step.label}\n </Text>\n </Box>\n );\n })}\n\n {status === \"success\" && (\n <Box marginTop={1}>\n <Text color=\"green\" bold>\n ✓ Build completed successfully!\n </Text>\n </Box>\n )}\n\n {status === \"error\" && error && (\n <Box marginTop={1} flexDirection=\"column\">\n <Text color=\"red\" bold>\n ✗ Build failed\n </Text>\n <Box marginTop={1} flexDirection=\"column\">\n {error.split(\"\\n\").map((line) => (\n <Text key={line} color=\"red\">\n {line}\n </Text>\n ))}\n </Box>\n </Box>\n )}\n </Box>\n );\n };\n\n render(<App />, {\n exitOnCtrlC: true,\n patchConsole: false,\n });\n }\n}\n"]}
@@ -3,12 +3,15 @@ import { Command, Flags } from "@oclif/core";
3
3
  import { Box, render, Text } from "ink";
4
4
  import { resolvePort } from "../cli/detect-port.js";
5
5
  import { Header } from "../cli/header.js";
6
+ import { resolveViewsDir } from "../cli/resolve-views-dir.js";
6
7
  import { startTunnelControlServer } from "../cli/tunnel-control-server.js";
7
8
  import { useMessages } from "../cli/use-messages.js";
8
9
  import { useNodemon } from "../cli/use-nodemon.js";
9
10
  import { useOpenBrowser } from "../cli/use-open-browser.js";
11
+ import { useOpenTunnelBrowser } from "../cli/use-open-tunnel-browser.js";
10
12
  import { useTunnel } from "../cli/use-tunnel.js";
11
13
  import { useTypeScriptCheck } from "../cli/use-typescript-check.js";
14
+ import { scanAndWriteViewsDts } from "../web/plugin/scan-views.js";
12
15
  export default class Dev extends Command {
13
16
  static description = "Start development server";
14
17
  static examples = ["skybridge"];
@@ -35,6 +38,20 @@ export default class Dev extends Command {
35
38
  };
36
39
  async run() {
37
40
  const { flags } = await this.parse(Dev);
41
+ // Generate .skybridge/views.d.ts before render() spawns `tsc --noEmit
42
+ // --watch`. Vite's plugin config hook writes the same file when nodemon
43
+ // boots the server, but tsc starts in parallel — if .skybridge/ doesn't
44
+ // exist at tsc startup, its watcher never picks up the late-created file
45
+ // and the dev UI reports phantom TS errors forever.
46
+ const root = process.cwd();
47
+ try {
48
+ scanAndWriteViewsDts(root, await resolveViewsDir(root));
49
+ }
50
+ catch {
51
+ // Best-effort: if the scan fails (e.g. broken vite config, duplicate
52
+ // view names) tsc may show phantom errors, but the dev server should
53
+ // still start so the developer can fix the underlying issue.
54
+ }
38
55
  const { port, fallback, envWarning } = await resolvePort(flags.port);
39
56
  if (envWarning) {
40
57
  this.warn(envWarning);
@@ -49,8 +66,9 @@ export default class Dev extends Command {
49
66
  const tsErrors = useTypeScriptCheck();
50
67
  const [messages, pushMessage] = useMessages();
51
68
  useNodemon(env, pushMessage);
52
- useOpenBrowser(port, flags.open);
69
+ useOpenBrowser(port, flags.open && !flags.tunnel);
53
70
  const tunnelState = useTunnel(port, pushMessage, flags.verbose, flags.tunnel);
71
+ useOpenTunnelBrowser(tunnelState, flags.open && flags.tunnel);
54
72
  return (_jsxs(Box, { flexDirection: "column", padding: 1, marginLeft: 1, children: [_jsx(Header, { version: this.config.version }), _jsxs(Box, { children: [_jsxs(Text, { children: ["\uD83C\uDFE0", " "] }), fallback ? (_jsx(Text, { color: "yellow", children: "3000 in use, running on " })) : (_jsx(Text, { children: "Running on " })), _jsx(Text, { color: "green", children: `http://localhost:${port}/mcp` })] }), _jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: "#20a832", children: ["\u2192", " "] }), _jsxs(Text, { color: "white", bold: true, children: ["Test locally with DevTools:", " "] }), _jsx(Text, { color: "green", children: `http://localhost:${port}/` })] }), tunnelState.status === "idle" && (_jsxs(Box, { children: [_jsxs(Text, { children: ["\uD83C\uDF0D", " "] }), _jsx(Text, { children: "Get a public URL and LLM Playground access with " }), _jsx(Text, { color: "cyan", bold: true, children: "--tunnel" }), _jsx(Text, { children: "." })] })), tunnelState.status === "starting" && (_jsxs(Box, { children: [_jsxs(Text, { children: ["\uD83C\uDF0D", " "] }), _jsx(Text, { color: "yellow", children: tunnelState.message })] })), tunnelState.status === "connected" && (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { children: ["\uD83C\uDF0D", " "] }), _jsx(Text, { children: "Exposed on " }), _jsx(Text, { color: "green", children: `${tunnelState.url}/mcp` })] }), _jsxs(Box, { children: [_jsxs(Text, { color: "#20a832", children: ["\u2192", " "] }), _jsxs(Text, { color: "white", bold: true, children: ["Test with an LLM on Playground:", " "] }), _jsx(Text, { color: "green", children: `${tunnelState.url}/try` })] })] })), tunnelState.status === "error" && (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { children: ["\uD83C\uDF0D", " "] }), _jsxs(Text, { color: "red", children: ["Cannot open tunnel: ", tunnelState.message] })] }), _jsxs(Box, { children: [_jsxs(Text, { color: "#20a832", children: ["\u2192", " "] }), _jsx(Text, { color: "red", children: `Try manually: npx alpic tunnel --port ${port}` })] })] })), _jsxs(Box, { children: [_jsxs(Text, { children: ["\uD83D\uDEDF", " "] }), _jsx(Text, { children: "Need help? Reach us on " }), _jsx(Text, { color: "white", underline: true, children: "https://discord.alpic.ai" })] }), tsErrors.length > 0 && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "red", bold: true, children: "\u26A0\uFE0F TypeScript errors found:" }), tsErrors.map((error) => (_jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "white", children: error.file }), _jsxs(Text, { color: "grey", children: ["(", error.line, ",", error.col, "):", " "] })] }), _jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "red", children: error.message }) })] }, `${error.file}:${error.line}:${error.col}`)))] })), messages.length > 0 && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "white", bold: true, children: "Logs:" }), messages.map((message) => (_jsx(Box, { marginLeft: 2, children: message.type === "restart" ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: "green", children: ["\u2713", " "] }), _jsx(Text, { color: "white", children: message.text })] })) : message.type === "error" ? (_jsx(Text, { color: "red", children: message.text })) : (_jsx(Text, { children: message.text })) }, message.id)))] }))] }));
55
73
  };
56
74
  // Note: `exitOnCtrlC: false` because we own SIGINT below to guarantee