freestyle-sandboxes 0.0.96 → 0.1.0
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 +39 -48
- package/index.cjs +4362 -0
- package/index.d.cts +9401 -0
- package/index.d.mts +9401 -0
- package/index.mjs +4348 -0
- package/package.json +16 -109
- package/dist/ai/inde.d.cts +0 -75
- package/dist/ai/inde.d.mts +0 -75
- package/dist/ai/index.cjs +0 -13
- package/dist/ai/index.d.cts +0 -75
- package/dist/ai/index.d.mts +0 -75
- package/dist/ai/index.mjs +0 -4
- package/dist/expo/inde.d.cts +0 -6
- package/dist/expo/inde.d.mts +0 -6
- package/dist/expo/index.cjs +0 -319
- package/dist/expo/index.d.cts +0 -6
- package/dist/expo/index.d.mts +0 -6
- package/dist/expo/index.mjs +0 -297
- package/dist/inde.d.cts +0 -373
- package/dist/inde.d.mts +0 -373
- package/dist/index-BKAG8L-o.mjs +0 -3061
- package/dist/index-DuOpIaWc.cjs +0 -3068
- package/dist/index.cjs +0 -1329
- package/dist/index.d-9H_wnIbz.d.ts +0 -4223
- package/dist/index.d.cts +0 -373
- package/dist/index.d.mts +0 -373
- package/dist/index.mjs +0 -1327
- package/dist/langgraph/inde.d.cts +0 -4180
- package/dist/langgraph/inde.d.mts +0 -4180
- package/dist/langgraph/index.cjs +0 -17155
- package/dist/langgraph/index.d.cts +0 -4180
- package/dist/langgraph/index.d.mts +0 -4180
- package/dist/langgraph/index.mjs +0 -17153
- package/dist/mastra/inde.d.cts +0 -2623
- package/dist/mastra/inde.d.mts +0 -2623
- package/dist/mastra/index.cjs +0 -55
- package/dist/mastra/index.d.cts +0 -2623
- package/dist/mastra/index.d.mts +0 -2623
- package/dist/mastra/index.mjs +0 -53
- package/dist/react/dev-server/index..d.cts +0 -33
- package/dist/react/dev-server/index..d.mts +0 -33
- package/dist/react/dev-server/index.cjs +0 -148
- package/dist/react/dev-server/index.d.cts +0 -33
- package/dist/react/dev-server/index.d.mts +0 -33
- package/dist/react/dev-server/index.mjs +0 -145
- package/dist/types.gen-CJa21P0C.d.ts +0 -1902
- package/dist/types.gen-DKjMRuu5.d.ts +0 -1898
- package/dist/utils/inde.d.cts +0 -10
- package/dist/utils/inde.d.mts +0 -10
- package/dist/utils/index.cjs +0 -100
- package/dist/utils/index.d.cts +0 -10
- package/dist/utils/index.d.mts +0 -10
- package/dist/utils/index.mjs +0 -75
- package/openapi/index.ts +0 -3
- package/openapi/sdk.gen.ts +0 -929
- package/openapi/types.gen.ts +0 -2234
- package/openapi-ts.config.ts +0 -7
- package/openapi.json +0 -1
- package/src/ai/index.ts +0 -164
- package/src/dev-server.ts +0 -95
- package/src/expo/_expo_internals.ts +0 -389
- package/src/expo/index.ts +0 -26
- package/src/index.ts +0 -1459
- package/src/langgraph/index.ts +0 -33
- package/src/mastra/index.ts +0 -38
- package/src/react/dev-server/index.tsx +0 -195
- package/src/react/dev-server/types.ts +0 -5
- package/src/utils/index.ts +0 -97
- package/tsconfig.json +0 -8
package/src/ai/index.ts
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
FreestyleDeployWebConfiguration,
|
|
3
|
-
FreestyleExecuteScriptParamsConfiguration,
|
|
4
|
-
FreestyleExecuteScriptResultSuccess,
|
|
5
|
-
HandleExecuteScriptError,
|
|
6
|
-
} from "../../openapi";
|
|
7
|
-
import { FreestyleSandboxes } from "..";
|
|
8
|
-
import { tool } from "ai";
|
|
9
|
-
import { z } from "zod";
|
|
10
|
-
|
|
11
|
-
export const executeCodeSchema = z.object({
|
|
12
|
-
script: z.string().describe(`
|
|
13
|
-
The JavaScript or TypeScript script to execute, must be in the format of:
|
|
14
|
-
|
|
15
|
-
import { someModule } from "someModule";
|
|
16
|
-
export default () => {
|
|
17
|
-
... your code here ...
|
|
18
|
-
return output;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
or for async functions:
|
|
22
|
-
|
|
23
|
-
import { someModule } from "someModule";
|
|
24
|
-
|
|
25
|
-
export default async () => {
|
|
26
|
-
... your code here ...
|
|
27
|
-
return output;
|
|
28
|
-
}
|
|
29
|
-
`),
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
export const executeCodeDescription = (envVars: string, nodeModules: string) =>
|
|
33
|
-
`Execute a JavaScript or TypeScript script.\n${
|
|
34
|
-
envVars.length > 0
|
|
35
|
-
? `You can use the following environment variables: ${envVars}`
|
|
36
|
-
: ""
|
|
37
|
-
}\n${
|
|
38
|
-
nodeModules.length > 0
|
|
39
|
-
? `You can use the following node modules: ${nodeModules}`
|
|
40
|
-
: "You cannot use any node modules."
|
|
41
|
-
}`;
|
|
42
|
-
/**
|
|
43
|
-
* Execute a JavaScript or TypeScript script
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* @param config - Configuration for the tool
|
|
47
|
-
* @param config.apiKey - The API key to use
|
|
48
|
-
* @param {Function} [config.onResult] - Optional callback function to handle the result.
|
|
49
|
-
* @param {boolean} [config.truncateOutput=false] - Whether to truncate the result to 1000 characters and truncate individual logs to the first 250 characters; useful to prevent long outputs from filling the context window.
|
|
50
|
-
*/
|
|
51
|
-
export const executeTool = (
|
|
52
|
-
config: FreestyleExecuteScriptParamsConfiguration & {
|
|
53
|
-
apiKey: string;
|
|
54
|
-
onResult?: (_v: {
|
|
55
|
-
toolCallId: string;
|
|
56
|
-
input: {
|
|
57
|
-
script: string;
|
|
58
|
-
[key: string]: unknown;
|
|
59
|
-
};
|
|
60
|
-
result: FreestyleExecuteScriptResultSuccess | HandleExecuteScriptError;
|
|
61
|
-
}) => void | Promise<void>;
|
|
62
|
-
truncateOutput?: boolean;
|
|
63
|
-
}
|
|
64
|
-
) => {
|
|
65
|
-
const api = new FreestyleSandboxes({
|
|
66
|
-
...config,
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
const envVars = Object.keys(config.envVars ?? {}).join(", ");
|
|
70
|
-
const nodeModules = Object.keys(config.nodeModules ?? {}).join(", ");
|
|
71
|
-
return tool({
|
|
72
|
-
description: executeCodeDescription(envVars, nodeModules),
|
|
73
|
-
parameters: executeCodeSchema,
|
|
74
|
-
execute: async ({ script, ...otherParams }, { toolCallId }) => {
|
|
75
|
-
try {
|
|
76
|
-
const res = await api.executeScript(script, config);
|
|
77
|
-
if (config.onResult) {
|
|
78
|
-
await config.onResult({
|
|
79
|
-
toolCallId,
|
|
80
|
-
result: res,
|
|
81
|
-
input: {
|
|
82
|
-
script,
|
|
83
|
-
...otherParams,
|
|
84
|
-
},
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
if (config.truncateOutput) {
|
|
88
|
-
if ("output" in res) {
|
|
89
|
-
res.result = JSON.stringify(res.result).slice(0, 1000);
|
|
90
|
-
res.logs = res.logs.slice(0, 1000);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return res;
|
|
94
|
-
} catch (e) {
|
|
95
|
-
console.log("ERROR: ", e.message);
|
|
96
|
-
return {
|
|
97
|
-
message: e.message,
|
|
98
|
-
error: e.error,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
});
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Deploy a Web project
|
|
107
|
-
* @param config - Configuration for the tool
|
|
108
|
-
* @param config.apiKey - The API key to use
|
|
109
|
-
*/
|
|
110
|
-
export const deployWebTool = (
|
|
111
|
-
config: FreestyleDeployWebConfiguration & {
|
|
112
|
-
apiKey: string;
|
|
113
|
-
}
|
|
114
|
-
) => {
|
|
115
|
-
const api = new FreestyleSandboxes({
|
|
116
|
-
...config,
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
const envVars = Object.keys(config.envVars ?? {}).join(", ");
|
|
120
|
-
const nodeModules = Object.keys(config.nodeModules ?? {}).join(", ");
|
|
121
|
-
return tool({
|
|
122
|
-
description: `Deploy a Web project. ${
|
|
123
|
-
envVars.length > 0
|
|
124
|
-
? `You can use the following environment variables: ${envVars}`
|
|
125
|
-
: ""
|
|
126
|
-
}\n${
|
|
127
|
-
nodeModules.length > 0
|
|
128
|
-
? `You can use the following node modules: ${nodeModules}`
|
|
129
|
-
: "You cannot use any node modules."
|
|
130
|
-
}`,
|
|
131
|
-
parameters: z.object({
|
|
132
|
-
files: z.record(z.string()).describe(`
|
|
133
|
-
A record of file names and their contents to deploy. For example:
|
|
134
|
-
{
|
|
135
|
-
"index.js": "import http from 'node:http';\\nnconsole.log('starting server');\\n\\nconst server = http.createServer(async(req, res) => {\\n res.writeHead(200, { 'Content-Type': 'text/plain' });\\n res.end('Welcome to New York its been waiting for you');\\n});\\n\\nserver.listen(3000, () => {\\n console.log('Server is running at http://localhost:3000');\\n});",
|
|
136
|
-
}
|
|
137
|
-
`),
|
|
138
|
-
}),
|
|
139
|
-
execute: async ({ files }) => {
|
|
140
|
-
// map from record<string, string> to record<string, {content: string}>
|
|
141
|
-
const new_files = Object.keys(files).reduce((acc, key) => {
|
|
142
|
-
acc[key] = { content: files[key] };
|
|
143
|
-
return acc;
|
|
144
|
-
}, {} as Record<string, { content: string }>);
|
|
145
|
-
try {
|
|
146
|
-
const res = await api.deployWeb(
|
|
147
|
-
{
|
|
148
|
-
kind: "files",
|
|
149
|
-
files: new_files,
|
|
150
|
-
},
|
|
151
|
-
config
|
|
152
|
-
);
|
|
153
|
-
return res;
|
|
154
|
-
} catch (e) {
|
|
155
|
-
console.log("ERROR: ", e.message);
|
|
156
|
-
return `Error deploying web project:\n\n${JSON.stringify(
|
|
157
|
-
files,
|
|
158
|
-
null,
|
|
159
|
-
2
|
|
160
|
-
)}\n\nError: ${e.message}`;
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
});
|
|
164
|
-
};
|
package/src/dev-server.ts
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
export interface FreestyleDevServer {
|
|
2
|
-
/**
|
|
3
|
-
* The URL for the dev server's HTTP API.
|
|
4
|
-
*/
|
|
5
|
-
ephemeralUrl: string;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* The URL to the MCP endpoint for the dev server.
|
|
9
|
-
*/
|
|
10
|
-
mcpEphemeralUrl: string;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* The URL for the VSCode server running in the dev server.
|
|
14
|
-
*/
|
|
15
|
-
codeServerUrl: string;
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Whether the dev server was just created.
|
|
19
|
-
*/
|
|
20
|
-
isNew: boolean;
|
|
21
|
-
|
|
22
|
-
fs: FreestyleDevServerFilesystem;
|
|
23
|
-
|
|
24
|
-
process: FreestyleDevServerProcess;
|
|
25
|
-
|
|
26
|
-
devCommandRunning: boolean;
|
|
27
|
-
installCommandRunning: boolean;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Get the status of the dev server
|
|
31
|
-
*/
|
|
32
|
-
status(): Promise<{
|
|
33
|
-
installing: boolean;
|
|
34
|
-
devRunning: boolean;
|
|
35
|
-
}>;
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Commit and push changes to the dev server repository
|
|
39
|
-
* @param message The commit message
|
|
40
|
-
*/
|
|
41
|
-
commitAndPush(message: string): Promise<void>;
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Shutdown the dev server
|
|
45
|
-
*/
|
|
46
|
-
shutdown(): Promise<{
|
|
47
|
-
success: boolean;
|
|
48
|
-
message: string;
|
|
49
|
-
}>;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface FreestyleDevServerFilesystem {
|
|
53
|
-
/**
|
|
54
|
-
* List files in the dev server directory
|
|
55
|
-
*/
|
|
56
|
-
ls(path?: string): Promise<Array<string>>;
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Read a file from the dev server
|
|
60
|
-
* @param path The path to the file
|
|
61
|
-
* @param encoding The encoding to use (defaults to utf-8)
|
|
62
|
-
*/
|
|
63
|
-
readFile(path: string, encoding?: string): Promise<string>;
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Write a file to the dev server
|
|
67
|
-
* @param path The path to write to
|
|
68
|
-
* @param content The content to write
|
|
69
|
-
* @param encoding The encoding to use (defaults to utf-8)
|
|
70
|
-
*/
|
|
71
|
-
writeFile(
|
|
72
|
-
path: string,
|
|
73
|
-
content: string | ArrayBufferLike,
|
|
74
|
-
encoding?: string
|
|
75
|
-
): Promise<void>;
|
|
76
|
-
|
|
77
|
-
watch(): AsyncGenerator<{ eventType: string; filename: string }>;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export interface FreestyleDevServerProcess {
|
|
81
|
-
/**
|
|
82
|
-
* Execute a command on the dev server
|
|
83
|
-
* @param cmd The command to execute
|
|
84
|
-
* @param background Whether to run the command in the background
|
|
85
|
-
*/
|
|
86
|
-
exec(
|
|
87
|
-
cmd: string,
|
|
88
|
-
background?: boolean
|
|
89
|
-
): Promise<{
|
|
90
|
-
id: string;
|
|
91
|
-
isNew: boolean;
|
|
92
|
-
stdout?: string[];
|
|
93
|
-
stderr?: string[];
|
|
94
|
-
}>;
|
|
95
|
-
}
|
|
@@ -1,389 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ExpoRoutesManifestV1,
|
|
3
|
-
RouteInfo,
|
|
4
|
-
} from "expo-router/build/routes-manifest";
|
|
5
|
-
import fs from "node:fs";
|
|
6
|
-
import path from "node:path";
|
|
7
|
-
|
|
8
|
-
import { createRequire } from "module";
|
|
9
|
-
const require = createRequire(import.meta.url);
|
|
10
|
-
// import { ExpoRouterServerManifestV1FunctionRoute } from "./types";
|
|
11
|
-
|
|
12
|
-
const debug = console.log;
|
|
13
|
-
|
|
14
|
-
function getProcessedManifest(path: string): ExpoRoutesManifestV1<RegExp> {
|
|
15
|
-
// TODO: JSON Schema for validation
|
|
16
|
-
const routesManifest = JSON.parse(
|
|
17
|
-
fs.readFileSync(path, "utf-8")
|
|
18
|
-
) as ExpoRoutesManifestV1;
|
|
19
|
-
|
|
20
|
-
const parsed: ExpoRoutesManifestV1<RegExp> = {
|
|
21
|
-
...routesManifest,
|
|
22
|
-
notFoundRoutes: routesManifest.notFoundRoutes.map((value: any) => {
|
|
23
|
-
return {
|
|
24
|
-
...value,
|
|
25
|
-
namedRegex: new RegExp(value.namedRegex),
|
|
26
|
-
};
|
|
27
|
-
}),
|
|
28
|
-
apiRoutes: routesManifest.apiRoutes.map((value: any) => {
|
|
29
|
-
return {
|
|
30
|
-
...value,
|
|
31
|
-
namedRegex: new RegExp(value.namedRegex),
|
|
32
|
-
};
|
|
33
|
-
}),
|
|
34
|
-
htmlRoutes: routesManifest.htmlRoutes.map((value: any) => {
|
|
35
|
-
return {
|
|
36
|
-
...value,
|
|
37
|
-
namedRegex: new RegExp(value.namedRegex),
|
|
38
|
-
};
|
|
39
|
-
}),
|
|
40
|
-
redirects: routesManifest.redirects?.map((value: any) => {
|
|
41
|
-
return {
|
|
42
|
-
...value,
|
|
43
|
-
namedRegex: new RegExp(value.namedRegex),
|
|
44
|
-
};
|
|
45
|
-
}),
|
|
46
|
-
rewrites: routesManifest.rewrites?.map((value: any) => {
|
|
47
|
-
return {
|
|
48
|
-
...value,
|
|
49
|
-
namedRegex: new RegExp(value.namedRegex),
|
|
50
|
-
};
|
|
51
|
-
}),
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
return parsed;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function getRoutesManifest(distFolder: string) {
|
|
58
|
-
return getProcessedManifest(path.join(distFolder, "_expo/routes.json"));
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// TODO: Reuse this for dev as well
|
|
62
|
-
export function createRequestHandler(
|
|
63
|
-
distFolder: string,
|
|
64
|
-
{
|
|
65
|
-
getRoutesManifest: getInternalRoutesManifest,
|
|
66
|
-
getHtml = async (_request, route) => {
|
|
67
|
-
// Serve a static file by exact route name
|
|
68
|
-
const filePath = path.join(distFolder, route.page + ".html");
|
|
69
|
-
if (fs.existsSync(filePath)) {
|
|
70
|
-
return fs.readFileSync(filePath, "utf-8");
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Serve a static file by route name with hoisted index
|
|
74
|
-
// See: https://github.com/expo/expo/pull/27935
|
|
75
|
-
const hoistedFilePath = route.page.match(/\/index$/)
|
|
76
|
-
? path.join(distFolder, route.page.replace(/\/index$/, "") + ".html")
|
|
77
|
-
: null;
|
|
78
|
-
if (hoistedFilePath && fs.existsSync(hoistedFilePath)) {
|
|
79
|
-
return fs.readFileSync(hoistedFilePath, "utf-8");
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return null;
|
|
83
|
-
},
|
|
84
|
-
getApiRoute = async (route) => {
|
|
85
|
-
const filePath = path.join(distFolder, route.file);
|
|
86
|
-
|
|
87
|
-
debug(`Handling API route: ${route.page}: ${filePath}`);
|
|
88
|
-
|
|
89
|
-
// TODO: What's the standard behavior for malformed projects?
|
|
90
|
-
if (!fs.existsSync(filePath)) {
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (/\.c?js$/.test(filePath)) {
|
|
95
|
-
return require(filePath);
|
|
96
|
-
}
|
|
97
|
-
return import(filePath);
|
|
98
|
-
},
|
|
99
|
-
logApiRouteExecutionError = (error: Error) => {
|
|
100
|
-
console.error(error);
|
|
101
|
-
},
|
|
102
|
-
handleApiRouteError = async (error: Error) => {
|
|
103
|
-
if ("statusCode" in error && typeof error.statusCode === "number") {
|
|
104
|
-
return new Response(error.message, {
|
|
105
|
-
status: error.statusCode,
|
|
106
|
-
headers: {
|
|
107
|
-
"Content-Type": "text/plain",
|
|
108
|
-
},
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
return new Response("Internal server error", {
|
|
112
|
-
status: 500,
|
|
113
|
-
headers: {
|
|
114
|
-
"Content-Type": "text/plain",
|
|
115
|
-
},
|
|
116
|
-
});
|
|
117
|
-
},
|
|
118
|
-
}: {
|
|
119
|
-
getHtml?: (
|
|
120
|
-
request: Request,
|
|
121
|
-
route: RouteInfo<RegExp>
|
|
122
|
-
) => Promise<string | Response | null>;
|
|
123
|
-
getRoutesManifest?: (
|
|
124
|
-
distFolder: string
|
|
125
|
-
) => Promise<ExpoRoutesManifestV1<RegExp> | null>;
|
|
126
|
-
getApiRoute?: (route: RouteInfo<RegExp>) => Promise<any>;
|
|
127
|
-
logApiRouteExecutionError?: (error: Error) => void;
|
|
128
|
-
handleApiRouteError?: (error: Error) => Promise<Response>;
|
|
129
|
-
} = {}
|
|
130
|
-
) {
|
|
131
|
-
let routesManifest: ExpoRoutesManifestV1<RegExp> | undefined;
|
|
132
|
-
|
|
133
|
-
return async function handler(request: Request): Promise<Response> {
|
|
134
|
-
if (getInternalRoutesManifest) {
|
|
135
|
-
const manifest = await getInternalRoutesManifest(distFolder);
|
|
136
|
-
if (manifest) {
|
|
137
|
-
routesManifest = manifest;
|
|
138
|
-
} else {
|
|
139
|
-
// Development error when Expo Router is not setup.
|
|
140
|
-
return new Response("No routes manifest found", {
|
|
141
|
-
status: 404,
|
|
142
|
-
headers: {
|
|
143
|
-
"Content-Type": "text/plain",
|
|
144
|
-
},
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
} else if (!routesManifest) {
|
|
148
|
-
routesManifest = getRoutesManifest(distFolder);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const url = new URL(request.url, "http://expo.dev");
|
|
152
|
-
|
|
153
|
-
const sanitizedPathname = url.pathname;
|
|
154
|
-
|
|
155
|
-
debug("Request", sanitizedPathname);
|
|
156
|
-
|
|
157
|
-
if (routesManifest.rewrites) {
|
|
158
|
-
for (const route of routesManifest.rewrites) {
|
|
159
|
-
if (!route.namedRegex.test(sanitizedPathname)) {
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const url = getRedirectRewriteLocation(request, route);
|
|
164
|
-
|
|
165
|
-
if (url) {
|
|
166
|
-
request = new Request(
|
|
167
|
-
new URL(url, new URL(request.url).origin),
|
|
168
|
-
request
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (routesManifest.redirects) {
|
|
175
|
-
for (const route of routesManifest.redirects) {
|
|
176
|
-
if (!route.namedRegex.test(sanitizedPathname)) {
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const Location = getRedirectRewriteLocation(request, route);
|
|
181
|
-
|
|
182
|
-
if (Location) {
|
|
183
|
-
debug("Redirecting", Location);
|
|
184
|
-
|
|
185
|
-
// Get the params
|
|
186
|
-
return new Response(null, {
|
|
187
|
-
status: route.permanent ? 308 : 307,
|
|
188
|
-
headers: {
|
|
189
|
-
Location,
|
|
190
|
-
},
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (request.method === "GET" || request.method === "HEAD") {
|
|
197
|
-
// First test static routes
|
|
198
|
-
for (const route of routesManifest.htmlRoutes) {
|
|
199
|
-
if (!route.namedRegex.test(sanitizedPathname)) {
|
|
200
|
-
continue;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// // Mutate to add the expoUrl object.
|
|
204
|
-
updateRequestWithConfig(request, route);
|
|
205
|
-
|
|
206
|
-
// serve a static file
|
|
207
|
-
const contents = await getHtml(request, route);
|
|
208
|
-
|
|
209
|
-
// TODO: What's the standard behavior for malformed projects?
|
|
210
|
-
if (!contents) {
|
|
211
|
-
return new Response("Not found", {
|
|
212
|
-
status: 404,
|
|
213
|
-
headers: {
|
|
214
|
-
"Content-Type": "text/plain",
|
|
215
|
-
},
|
|
216
|
-
});
|
|
217
|
-
} else if (contents instanceof Response) {
|
|
218
|
-
return contents;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return new Response(contents, {
|
|
222
|
-
status: 200,
|
|
223
|
-
headers: {
|
|
224
|
-
"Content-Type": "text/html",
|
|
225
|
-
},
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Next, test API routes
|
|
231
|
-
for (const route of routesManifest.apiRoutes) {
|
|
232
|
-
if (!route.namedRegex.test(sanitizedPathname)) {
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const func = await getApiRoute(route);
|
|
237
|
-
|
|
238
|
-
if (func instanceof Response) {
|
|
239
|
-
return func;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const routeHandler = func?.[request.method];
|
|
243
|
-
if (!routeHandler) {
|
|
244
|
-
return new Response("Method not allowed", {
|
|
245
|
-
status: 405,
|
|
246
|
-
headers: {
|
|
247
|
-
"Content-Type": "text/plain",
|
|
248
|
-
},
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Mutate to add the expoUrl object.
|
|
253
|
-
const params = updateRequestWithConfig(request, route);
|
|
254
|
-
|
|
255
|
-
try {
|
|
256
|
-
// TODO: Handle undefined
|
|
257
|
-
return (await routeHandler(request, params)) as Response;
|
|
258
|
-
} catch (error) {
|
|
259
|
-
if (error instanceof Error) {
|
|
260
|
-
logApiRouteExecutionError(error);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return handleApiRouteError(error as Error);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Finally, test 404 routes
|
|
268
|
-
for (const route of routesManifest.notFoundRoutes) {
|
|
269
|
-
if (!route.namedRegex.test(sanitizedPathname)) {
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// // Mutate to add the expoUrl object.
|
|
274
|
-
updateRequestWithConfig(request, route);
|
|
275
|
-
|
|
276
|
-
// serve a static file
|
|
277
|
-
const contents = await getHtml(request, route);
|
|
278
|
-
|
|
279
|
-
// TODO: What's the standard behavior for malformed projects?
|
|
280
|
-
if (!contents) {
|
|
281
|
-
return new Response("Not found", {
|
|
282
|
-
status: 404,
|
|
283
|
-
headers: {
|
|
284
|
-
"Content-Type": "text/plain",
|
|
285
|
-
},
|
|
286
|
-
});
|
|
287
|
-
} else if (contents instanceof Response) {
|
|
288
|
-
return contents;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
return new Response(contents, {
|
|
292
|
-
status: 404,
|
|
293
|
-
headers: {
|
|
294
|
-
"Content-Type": "text/html",
|
|
295
|
-
},
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// 404
|
|
300
|
-
const response = new Response("Not found", {
|
|
301
|
-
status: 404,
|
|
302
|
-
headers: {
|
|
303
|
-
"Content-Type": "text/plain",
|
|
304
|
-
},
|
|
305
|
-
});
|
|
306
|
-
return response;
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/** Match `[page]` -> `page` */
|
|
311
|
-
// Ported from `expo-router/src/matchers.tsx`
|
|
312
|
-
function matchDynamicName(name: string): string | undefined {
|
|
313
|
-
// Don't match `...` or `[` or `]` inside the brackets
|
|
314
|
-
// eslint-disable-next-line no-useless-escape
|
|
315
|
-
return name.match(/^\[([^[\](?:\.\.\.)]+?)\]$/)?.[1];
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/** Match `[...page]` -> `page` */
|
|
319
|
-
// Ported from `expo-router/src/matchers.tsx`
|
|
320
|
-
function matchDeepDynamicRouteName(name: string): string | undefined {
|
|
321
|
-
return name.match(/^\[\.\.\.([^/]+?)\]$/)?.[1];
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
function updateRequestWithConfig(
|
|
325
|
-
request: Request,
|
|
326
|
-
config: ExpoRouterServerManifestV1FunctionRoute
|
|
327
|
-
) {
|
|
328
|
-
const params: Record<string, string> = {};
|
|
329
|
-
const url = new URL(request.url);
|
|
330
|
-
const match = config.namedRegex.exec(url.pathname);
|
|
331
|
-
if (match?.groups) {
|
|
332
|
-
for (const [key, value] of Object.entries(match.groups)) {
|
|
333
|
-
const namedKey = config.routeKeys[key];
|
|
334
|
-
params[namedKey] = value;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
return params;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
function getRedirectRewriteLocation(
|
|
342
|
-
request: Request,
|
|
343
|
-
route: RouteInfo<RegExp>
|
|
344
|
-
) {
|
|
345
|
-
if (route.methods) {
|
|
346
|
-
if (!route.methods.includes(request.method)) {
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
const params = updateRequestWithConfig(request, route);
|
|
352
|
-
|
|
353
|
-
const urlSearchParams = new URL(request.url).searchParams;
|
|
354
|
-
|
|
355
|
-
let location = route.page
|
|
356
|
-
.split("/")
|
|
357
|
-
.map((segment) => {
|
|
358
|
-
let match = matchDynamicName(segment);
|
|
359
|
-
|
|
360
|
-
if (match) {
|
|
361
|
-
const value = params[match];
|
|
362
|
-
delete params[match];
|
|
363
|
-
// If we are redirecting from a catch-all route, we need to remove the extra segments
|
|
364
|
-
return value?.split("/")[0];
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
match = matchDeepDynamicRouteName(segment);
|
|
368
|
-
|
|
369
|
-
if (match) {
|
|
370
|
-
const value = params[match];
|
|
371
|
-
delete params[match];
|
|
372
|
-
return value;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
return segment;
|
|
376
|
-
})
|
|
377
|
-
.join("/");
|
|
378
|
-
|
|
379
|
-
if (Object.keys(params).length > 0 || urlSearchParams.size > 0) {
|
|
380
|
-
location +=
|
|
381
|
-
"?" +
|
|
382
|
-
new URLSearchParams({
|
|
383
|
-
...params,
|
|
384
|
-
...Object.fromEntries(urlSearchParams.entries()),
|
|
385
|
-
}).toString();
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
return location;
|
|
389
|
-
}
|
package/src/expo/index.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { createRequestHandler } from "./_expo_internals";
|
|
2
|
-
|
|
3
|
-
import * as path from "node:path";
|
|
4
|
-
import * as process from "node:process";
|
|
5
|
-
import { Hono } from "hono";
|
|
6
|
-
import { serveStatic } from "hono/deno";
|
|
7
|
-
|
|
8
|
-
export const freestyleExpoServer = ({
|
|
9
|
-
CLIENT_BUILD_DIR = path.join(process.cwd(), "dist/client"),
|
|
10
|
-
SERVER_BUILD_DIR = path.join(process.cwd(), "dist/server"),
|
|
11
|
-
} = {}) => {
|
|
12
|
-
// // Expo handler
|
|
13
|
-
const expoHandler = createRequestHandler(SERVER_BUILD_DIR);
|
|
14
|
-
|
|
15
|
-
const app = new Hono();
|
|
16
|
-
app.use("*", serveStatic({ root: CLIENT_BUILD_DIR }));
|
|
17
|
-
|
|
18
|
-
app.all("*", async (c, next) => {
|
|
19
|
-
console.log("Request received:", c.req.url);
|
|
20
|
-
const response = await expoHandler(c.req.raw);
|
|
21
|
-
return response;
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
// @ts-expect-error — Deno.serve is not in the types
|
|
25
|
-
Deno.serve(app.fetch);
|
|
26
|
-
};
|