@tschk/moonshine-deploy-vercel 0.3.5 → 0.4.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/package.json +8 -8
- package/src/index.ts +108 -60
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tschk/moonshine-deploy-vercel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"license": "ISC",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -15,16 +15,16 @@
|
|
|
15
15
|
"test": "bun test"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@tschk/moonshine-
|
|
19
|
-
"@tschk/moonshine-
|
|
20
|
-
"@tschk/moonshine-
|
|
21
|
-
"@tschk/moonshine-
|
|
22
|
-
"@tschk/moonshine-
|
|
23
|
-
"@tschk/moonshine-server": "^0.3.5"
|
|
18
|
+
"@tschk/moonshine-compiler": "^0.3.6",
|
|
19
|
+
"@tschk/moonshine-framework": "^0.3.6",
|
|
20
|
+
"@tschk/moonshine-react": "^0.3.6",
|
|
21
|
+
"@tschk/moonshine-router": "^0.3.6",
|
|
22
|
+
"@tschk/moonshine-server": "^0.3.6"
|
|
24
23
|
},
|
|
25
24
|
"devDependencies": {
|
|
26
25
|
"@types/bun": "1.3.14",
|
|
27
|
-
"typescript": "7.0.2"
|
|
26
|
+
"typescript": "7.0.2",
|
|
27
|
+
"@tschk/moonshine-adapter-conformance": "^0.3.6"
|
|
28
28
|
},
|
|
29
29
|
"files": [
|
|
30
30
|
"src"
|
package/src/index.ts
CHANGED
|
@@ -6,11 +6,9 @@ import type {
|
|
|
6
6
|
DeploymentAdapter,
|
|
7
7
|
MoonshineManifest,
|
|
8
8
|
RouteArtifact,
|
|
9
|
-
} from "@tschk/moonshine-framework";
|
|
10
|
-
import type {
|
|
11
9
|
Harness,
|
|
12
10
|
HarnessFactory,
|
|
13
|
-
} from "@tschk/moonshine-
|
|
11
|
+
} from "@tschk/moonshine-framework";
|
|
14
12
|
|
|
15
13
|
type VercelRoute = {
|
|
16
14
|
src: string;
|
|
@@ -63,19 +61,21 @@ async function copyAssets(
|
|
|
63
61
|
assets: MoonshineManifest["assets"],
|
|
64
62
|
outDir: string,
|
|
65
63
|
): Promise<void> {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
64
|
+
await Promise.all(
|
|
65
|
+
assets.map(async (asset) => {
|
|
66
|
+
const source = isAbsolute(asset.file)
|
|
67
|
+
? asset.file
|
|
68
|
+
: resolve(outDir, asset.file);
|
|
69
|
+
const target = resolve(
|
|
70
|
+
outDir,
|
|
71
|
+
(asset.path ?? asset.file).replace(/^\/+/, ""),
|
|
72
|
+
);
|
|
73
|
+
await mkdir(dirname(target), { recursive: true });
|
|
74
|
+
if (source !== target) {
|
|
75
|
+
await copyFile(source, target);
|
|
76
|
+
}
|
|
77
|
+
}),
|
|
78
|
+
);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
function validateRuntime(
|
|
@@ -114,9 +114,45 @@ function routeToRegex(path: string): string {
|
|
|
114
114
|
.replace(/:([^/]+)/g, "(?<$1>[^/]+)")}$`;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
function staticSlug(path: string): string {
|
|
117
|
+
function staticSlug(path: string, usedSlugs: Set<string>): string {
|
|
118
118
|
if (path === "/") return "index";
|
|
119
|
-
|
|
119
|
+
let slug = path.replace(/^\/+/, "").replace(/\//g, "-");
|
|
120
|
+
if (!usedSlugs.has(slug)) {
|
|
121
|
+
usedSlugs.add(slug);
|
|
122
|
+
return slug;
|
|
123
|
+
}
|
|
124
|
+
let i = 1;
|
|
125
|
+
while (usedSlugs.has(`${slug}-${i}`)) i++;
|
|
126
|
+
usedSlugs.add(`${slug}-${i}`);
|
|
127
|
+
return `${slug}-${i}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function mapConcurrent<T, R>(
|
|
131
|
+
items: T[],
|
|
132
|
+
fn: (item: T) => Promise<R>,
|
|
133
|
+
concurrency: number,
|
|
134
|
+
): Promise<R[]> {
|
|
135
|
+
const results = new Array<R>(items.length);
|
|
136
|
+
const errors: unknown[] = [];
|
|
137
|
+
let index = 0;
|
|
138
|
+
|
|
139
|
+
const worker = async () => {
|
|
140
|
+
while (index < items.length) {
|
|
141
|
+
const i = index++;
|
|
142
|
+
try {
|
|
143
|
+
results[i] = await fn(items[i]);
|
|
144
|
+
} catch (e) {
|
|
145
|
+
errors.push(e);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
await Promise.all(
|
|
151
|
+
Array.from({ length: Math.min(concurrency, items.length) }, worker),
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
if (errors.length > 0) throw errors[0];
|
|
155
|
+
return results;
|
|
120
156
|
}
|
|
121
157
|
|
|
122
158
|
async function prerenderRoute(route: RouteArtifact): Promise<string> {
|
|
@@ -155,31 +191,38 @@ export const vercelAdapter: DeploymentAdapter = {
|
|
|
155
191
|
await mkdir(functionsDir, { recursive: true });
|
|
156
192
|
|
|
157
193
|
const config: VercelConfig = { version: 3, routes: [] };
|
|
194
|
+
const usedSlugs = new Set<string>();
|
|
158
195
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
196
|
+
const routesConfig = await mapConcurrent(
|
|
197
|
+
resolvedManifest.routes,
|
|
198
|
+
async (route) => {
|
|
199
|
+
if (route.mode === "static" && !route.path.includes("*")) {
|
|
200
|
+
const html = await prerenderRoute(route);
|
|
201
|
+
const slug = staticSlug(route.path, usedSlugs);
|
|
202
|
+
await writeFile(resolve(staticDir, `${slug}.html`), html);
|
|
203
|
+
return {
|
|
204
|
+
src: routeToRegex(route.path),
|
|
205
|
+
dest: `/${slug}.html`,
|
|
206
|
+
};
|
|
207
|
+
} else {
|
|
208
|
+
const funcDir = resolve(functionsDir, `${route.id}.func`);
|
|
209
|
+
await mkdir(funcDir, { recursive: true });
|
|
171
210
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
211
|
+
const runtime =
|
|
212
|
+
route.runtime === "vercel-edge" ? "edge" : "nodejs20.x";
|
|
213
|
+
const vcConfig: Record<string, string> = {
|
|
214
|
+
runtime,
|
|
215
|
+
handler: "index.ts",
|
|
216
|
+
};
|
|
217
|
+
if (runtime !== "edge") {
|
|
218
|
+
vcConfig.launcherType = "Nodejs";
|
|
219
|
+
}
|
|
220
|
+
await writeFile(
|
|
221
|
+
resolve(funcDir, ".vc-config.json"),
|
|
222
|
+
JSON.stringify(vcConfig, null, 2) + "\n",
|
|
223
|
+
);
|
|
181
224
|
|
|
182
|
-
|
|
225
|
+
const functionEntry = `import { createRequestHandler } from "@tschk/moonshine-server";
|
|
183
226
|
import { reactRenderer } from "@tschk/moonshine-react";
|
|
184
227
|
import manifest from "../../manifest.json" with { type: "json" };
|
|
185
228
|
|
|
@@ -187,28 +230,33 @@ const fetch = createRequestHandler({ manifest, renderer: reactRenderer });
|
|
|
187
230
|
|
|
188
231
|
export default { fetch };
|
|
189
232
|
`;
|
|
190
|
-
|
|
233
|
+
await writeFile(resolve(funcDir, "index.ts"), functionEntry);
|
|
191
234
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
235
|
+
return {
|
|
236
|
+
src: routeToRegex(route.path),
|
|
237
|
+
dest: `/${route.id}`,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
20,
|
|
242
|
+
);
|
|
243
|
+
config.routes.push(...routesConfig);
|
|
198
244
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
245
|
+
await Promise.all(
|
|
246
|
+
resolvedManifest.assets.map(async (asset) => {
|
|
247
|
+
const source = isAbsolute(asset.file)
|
|
248
|
+
? asset.file
|
|
249
|
+
: resolve(outDir, asset.file);
|
|
250
|
+
const target = resolve(
|
|
251
|
+
staticDir,
|
|
252
|
+
(asset.path ?? asset.file).replace(/^\/+/, ""),
|
|
253
|
+
);
|
|
254
|
+
await mkdir(dirname(target), { recursive: true });
|
|
255
|
+
if (source !== target) {
|
|
256
|
+
await copyFile(source, target);
|
|
257
|
+
}
|
|
258
|
+
}),
|
|
259
|
+
);
|
|
212
260
|
|
|
213
261
|
await writeFile(
|
|
214
262
|
resolve(vercelOutput, "manifest.json"),
|