@prisma/compute-sdk 0.13.0 → 0.15.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/dist/astro-build.d.ts +14 -0
- package/dist/astro-build.d.ts.map +1 -0
- package/dist/astro-build.js +59 -0
- package/dist/astro-build.js.map +1 -0
- package/dist/auto-build.d.ts.map +1 -1
- package/dist/auto-build.js +6 -0
- package/dist/auto-build.js.map +1 -1
- package/dist/build-strategy.d.ts +30 -0
- package/dist/build-strategy.d.ts.map +1 -1
- package/dist/build-strategy.js +101 -1
- package/dist/build-strategy.js.map +1 -1
- package/dist/errors.d.ts +6 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +2 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/log-stream.d.ts +40 -0
- package/dist/log-stream.d.ts.map +1 -0
- package/dist/log-stream.js +151 -0
- package/dist/log-stream.js.map +1 -0
- package/dist/nextjs-build.d.ts.map +1 -1
- package/dist/nextjs-build.js +23 -87
- package/dist/nextjs-build.js.map +1 -1
- package/dist/nuxt-build.d.ts +14 -0
- package/dist/nuxt-build.d.ts.map +1 -0
- package/dist/nuxt-build.js +59 -0
- package/dist/nuxt-build.js.map +1 -0
- package/dist/tanstack-start-build.d.ts +14 -0
- package/dist/tanstack-start-build.d.ts.map +1 -0
- package/dist/tanstack-start-build.js +55 -0
- package/dist/tanstack-start-build.js.map +1 -0
- package/package.json +4 -2
- package/src/astro-build.ts +76 -0
- package/src/auto-build.ts +6 -0
- package/src/build-strategy.ts +141 -1
- package/src/errors.ts +5 -0
- package/src/index.ts +13 -5
- package/src/log-stream.ts +218 -0
- package/src/nextjs-build.ts +30 -109
- package/src/nuxt-build.ts +76 -0
- package/src/tanstack-start-build.ts +66 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import type { IncomingMessage } from "node:http";
|
|
2
|
+
import { Result } from "better-result";
|
|
3
|
+
import WebSocket from "ws";
|
|
4
|
+
import { CancelledError, LogStreamError } from "./errors.ts";
|
|
5
|
+
|
|
6
|
+
const isBun = "Bun" in globalThis;
|
|
7
|
+
|
|
8
|
+
export type LogRecord = {
|
|
9
|
+
type: "log";
|
|
10
|
+
text: string;
|
|
11
|
+
byteStart: number;
|
|
12
|
+
byteEnd: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type TerminalRecord = {
|
|
16
|
+
type: "terminal";
|
|
17
|
+
kind: "end" | "error";
|
|
18
|
+
code: string;
|
|
19
|
+
message: string;
|
|
20
|
+
retryable: boolean;
|
|
21
|
+
cursor: string | null;
|
|
22
|
+
details?: Record<string, unknown>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type StreamRecord = LogRecord | TerminalRecord;
|
|
26
|
+
|
|
27
|
+
export type LogStreamOptions = {
|
|
28
|
+
baseUrl: string;
|
|
29
|
+
token: string;
|
|
30
|
+
versionId: string;
|
|
31
|
+
tail?: number;
|
|
32
|
+
fromStart?: boolean;
|
|
33
|
+
cursor?: string;
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type StreamResult = {
|
|
38
|
+
terminal: TerminalRecord | null;
|
|
39
|
+
lastByteEnd: number | null;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type StreamLogsError = LogStreamError | CancelledError;
|
|
43
|
+
|
|
44
|
+
function parseErrorBody(body: string, statusCode: number): string {
|
|
45
|
+
try {
|
|
46
|
+
const parsed = JSON.parse(body) as {
|
|
47
|
+
message?: string;
|
|
48
|
+
error?: { message?: string };
|
|
49
|
+
};
|
|
50
|
+
return parsed.error?.message ?? parsed.message ?? `HTTP ${statusCode}`;
|
|
51
|
+
} catch {
|
|
52
|
+
return body || `HTTP ${statusCode}`;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function toError(value: unknown): Error {
|
|
57
|
+
if (value instanceof Error) return value;
|
|
58
|
+
|
|
59
|
+
// Bun emits ErrorEvent instead of Error on WebSocket failures
|
|
60
|
+
if (
|
|
61
|
+
typeof value === "object" &&
|
|
62
|
+
value !== null &&
|
|
63
|
+
"message" in value &&
|
|
64
|
+
typeof (value as { message: unknown }).message === "string"
|
|
65
|
+
) {
|
|
66
|
+
return new Error((value as { message: string }).message);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return new Error(String(value));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function buildWebSocketUrl(options: LogStreamOptions): string {
|
|
73
|
+
const httpUrl = new URL(
|
|
74
|
+
`/v1/compute-services/versions/${encodeURIComponent(options.versionId)}/logs`,
|
|
75
|
+
options.baseUrl,
|
|
76
|
+
);
|
|
77
|
+
httpUrl.protocol = httpUrl.protocol === "https:" ? "wss:" : "ws:";
|
|
78
|
+
|
|
79
|
+
if (options.tail != null)
|
|
80
|
+
httpUrl.searchParams.set("tail", String(options.tail));
|
|
81
|
+
if (options.fromStart) httpUrl.searchParams.set("from_start", "true");
|
|
82
|
+
if (options.cursor) httpUrl.searchParams.set("cursor", options.cursor);
|
|
83
|
+
|
|
84
|
+
return httpUrl.toString();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
type ConnectResult = {
|
|
88
|
+
terminal: TerminalRecord | null;
|
|
89
|
+
lastByteEnd: number | null;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
function connectOnce(
|
|
93
|
+
options: LogStreamOptions,
|
|
94
|
+
onRecord: (record: StreamRecord) => void,
|
|
95
|
+
): Promise<ConnectResult> {
|
|
96
|
+
return new Promise<ConnectResult>((resolve, reject) => {
|
|
97
|
+
if (options.signal?.aborted) {
|
|
98
|
+
reject(new CancelledError());
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const url = buildWebSocketUrl(options);
|
|
103
|
+
const ws = new WebSocket(url, {
|
|
104
|
+
headers: { Authorization: `Bearer ${options.token}` },
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
let terminal: TerminalRecord | null = null;
|
|
108
|
+
let lastByteEnd: number | null = null;
|
|
109
|
+
let settled = false;
|
|
110
|
+
|
|
111
|
+
const cleanup = () => {
|
|
112
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const onAbort = () => {
|
|
116
|
+
cleanup();
|
|
117
|
+
if (!settled) {
|
|
118
|
+
settled = true;
|
|
119
|
+
ws.close();
|
|
120
|
+
reject(new CancelledError());
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
124
|
+
|
|
125
|
+
if (options.signal?.aborted) {
|
|
126
|
+
onAbort();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Node.js ws: fires when the server responds with a non-101 status,
|
|
131
|
+
// giving us access to the HTTP response for structured error messages.
|
|
132
|
+
// Bun doesn't implement this event — errors go to the "error" handler.
|
|
133
|
+
if (!isBun) {
|
|
134
|
+
ws.on("unexpected-response", (_req: unknown, res: IncomingMessage) => {
|
|
135
|
+
const chunks: Buffer[] = [];
|
|
136
|
+
res.on("data", (chunk: Buffer) => {
|
|
137
|
+
chunks.push(chunk);
|
|
138
|
+
});
|
|
139
|
+
res.on("end", () => {
|
|
140
|
+
cleanup();
|
|
141
|
+
if (!settled) {
|
|
142
|
+
settled = true;
|
|
143
|
+
const statusCode = res.statusCode ?? 0;
|
|
144
|
+
const body = Buffer.concat(chunks).toString();
|
|
145
|
+
reject(
|
|
146
|
+
new LogStreamError({
|
|
147
|
+
statusCode,
|
|
148
|
+
message: parseErrorBody(body, statusCode),
|
|
149
|
+
}),
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
ws.on("message", (data: WebSocket.RawData) => {
|
|
157
|
+
let record: StreamRecord;
|
|
158
|
+
try {
|
|
159
|
+
record = JSON.parse(data.toString()) as StreamRecord;
|
|
160
|
+
} catch {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (record.type === "log") {
|
|
164
|
+
lastByteEnd = record.byteEnd;
|
|
165
|
+
} else if (record.type === "terminal") {
|
|
166
|
+
terminal = record;
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
onRecord(record);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
cleanup();
|
|
172
|
+
if (!settled) {
|
|
173
|
+
settled = true;
|
|
174
|
+
ws.close();
|
|
175
|
+
reject(toError(err));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
ws.on("close", () => {
|
|
181
|
+
cleanup();
|
|
182
|
+
if (!settled) {
|
|
183
|
+
settled = true;
|
|
184
|
+
resolve({ terminal, lastByteEnd });
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
ws.on("error", (err: unknown) => {
|
|
189
|
+
cleanup();
|
|
190
|
+
if (!settled) {
|
|
191
|
+
settled = true;
|
|
192
|
+
const error = toError(err);
|
|
193
|
+
reject(new LogStreamError({ statusCode: 0, message: error.message }));
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Open a WebSocket to the log-streaming endpoint and deliver records via callback.
|
|
201
|
+
*
|
|
202
|
+
* Returns `Ok` with the stream result on graceful close, or `Err` with a
|
|
203
|
+
* `LogStreamError` (connection/HTTP errors) or `CancelledError` (abort signal).
|
|
204
|
+
*/
|
|
205
|
+
export async function streamLogs(
|
|
206
|
+
options: LogStreamOptions,
|
|
207
|
+
onRecord: (record: StreamRecord) => void,
|
|
208
|
+
): Promise<Result<StreamResult, StreamLogsError>> {
|
|
209
|
+
return Result.tryPromise({
|
|
210
|
+
try: () => connectOnce(options, onRecord),
|
|
211
|
+
catch: (e) => {
|
|
212
|
+
if (CancelledError.is(e)) return e;
|
|
213
|
+
if (LogStreamError.is(e)) return e;
|
|
214
|
+
const error = toError(e);
|
|
215
|
+
return new LogStreamError({ statusCode: 0, message: error.message });
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
}
|
package/src/nextjs-build.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { cp, mkdtemp, readdir, readFile, rm, stat } from "node:fs/promises";
|
|
1
|
+
import { cp, mkdtemp, rm, stat } from "node:fs/promises";
|
|
3
2
|
import os from "node:os";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
import type { BuildArtifact, BuildStrategy } from "./build-strategy.ts";
|
|
5
|
+
import {
|
|
6
|
+
hasPackageDependency,
|
|
7
|
+
hasRootFile,
|
|
8
|
+
runPackageCli,
|
|
9
|
+
} from "./build-strategy.ts";
|
|
6
10
|
|
|
7
11
|
const NEXT_CONFIG_FILENAMES = [
|
|
8
12
|
"next.config.js",
|
|
@@ -23,11 +27,21 @@ export class NextjsBuild implements BuildStrategy {
|
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
async canBuild(): Promise<boolean> {
|
|
26
|
-
return (
|
|
30
|
+
return (
|
|
31
|
+
(await hasRootFile(this.#appPath, NEXT_CONFIG_FILENAMES)) ||
|
|
32
|
+
(await hasPackageDependency(this.#appPath, ["next"]))
|
|
33
|
+
);
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
async execute(): Promise<BuildArtifact> {
|
|
30
|
-
await
|
|
37
|
+
await runPackageCli({
|
|
38
|
+
appPath: this.#appPath,
|
|
39
|
+
cliName: "next",
|
|
40
|
+
args: ["build"],
|
|
41
|
+
failurePrefix: "Next.js",
|
|
42
|
+
missingMessage:
|
|
43
|
+
"Could not find the Next.js CLI. Install it with `npm install next` or ensure npx/bunx is available.",
|
|
44
|
+
});
|
|
31
45
|
|
|
32
46
|
const standaloneDir = path.join(this.#appPath, ".next", "standalone");
|
|
33
47
|
const standaloneStat = await stat(standaloneDir).catch(() => null);
|
|
@@ -42,19 +56,24 @@ export class NextjsBuild implements BuildStrategy {
|
|
|
42
56
|
try {
|
|
43
57
|
const artifactDir = path.join(outDir, "app");
|
|
44
58
|
|
|
45
|
-
await cp(standaloneDir, artifactDir, {
|
|
59
|
+
await cp(standaloneDir, artifactDir, {
|
|
60
|
+
recursive: true,
|
|
61
|
+
verbatimSymlinks: true,
|
|
62
|
+
});
|
|
46
63
|
|
|
47
64
|
const publicDir = path.join(this.#appPath, "public");
|
|
48
|
-
if (await
|
|
65
|
+
if (await directoryExists(publicDir)) {
|
|
49
66
|
await cp(publicDir, path.join(artifactDir, "public"), {
|
|
50
67
|
recursive: true,
|
|
68
|
+
verbatimSymlinks: true,
|
|
51
69
|
});
|
|
52
70
|
}
|
|
53
71
|
|
|
54
72
|
const staticDir = path.join(this.#appPath, ".next", "static");
|
|
55
|
-
if (await
|
|
73
|
+
if (await directoryExists(staticDir)) {
|
|
56
74
|
await cp(staticDir, path.join(artifactDir, ".next", "static"), {
|
|
57
75
|
recursive: true,
|
|
76
|
+
verbatimSymlinks: true,
|
|
58
77
|
});
|
|
59
78
|
}
|
|
60
79
|
|
|
@@ -69,107 +88,9 @@ export class NextjsBuild implements BuildStrategy {
|
|
|
69
88
|
throw error;
|
|
70
89
|
}
|
|
71
90
|
}
|
|
91
|
+
}
|
|
72
92
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
entries = await readdir(this.#appPath);
|
|
77
|
-
} catch {
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
return entries.some((entry) => NEXT_CONFIG_FILENAMES.includes(entry));
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async #hasNextDependency(): Promise<boolean> {
|
|
84
|
-
const packageJsonPath = path.join(this.#appPath, "package.json");
|
|
85
|
-
|
|
86
|
-
let content: string;
|
|
87
|
-
try {
|
|
88
|
-
content = await readFile(packageJsonPath, "utf-8");
|
|
89
|
-
} catch {
|
|
90
|
-
return false;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
let parsed: { dependencies?: unknown; devDependencies?: unknown };
|
|
94
|
-
try {
|
|
95
|
-
parsed = JSON.parse(content) as {
|
|
96
|
-
dependencies?: unknown;
|
|
97
|
-
devDependencies?: unknown;
|
|
98
|
-
};
|
|
99
|
-
} catch {
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const deps =
|
|
104
|
-
typeof parsed.dependencies === "object" && parsed.dependencies !== null
|
|
105
|
-
? parsed.dependencies
|
|
106
|
-
: {};
|
|
107
|
-
const devDeps =
|
|
108
|
-
typeof parsed.devDependencies === "object" &&
|
|
109
|
-
parsed.devDependencies !== null
|
|
110
|
-
? parsed.devDependencies
|
|
111
|
-
: {};
|
|
112
|
-
|
|
113
|
-
return "next" in deps || "next" in devDeps;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async #runBuild(): Promise<void> {
|
|
117
|
-
const localBin = path.join(this.#appPath, "node_modules", ".bin", "next");
|
|
118
|
-
const candidates: Array<{ command: string; args: string[] }> = [
|
|
119
|
-
{ command: localBin, args: ["build"] },
|
|
120
|
-
{ command: "npx", args: ["next", "build"] },
|
|
121
|
-
{ command: "bunx", args: ["next", "build"] },
|
|
122
|
-
];
|
|
123
|
-
|
|
124
|
-
for (const { command, args } of candidates) {
|
|
125
|
-
try {
|
|
126
|
-
await this.#exec(command, args);
|
|
127
|
-
return;
|
|
128
|
-
} catch (error) {
|
|
129
|
-
if (
|
|
130
|
-
error instanceof Error &&
|
|
131
|
-
"code" in error &&
|
|
132
|
-
error.code === "ENOENT"
|
|
133
|
-
) {
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
throw error;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
throw new Error(
|
|
141
|
-
"Could not find the Next.js CLI. Install it with `npm install next` or ensure npx/bunx is available.",
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
#exec(command: string, args: string[]): Promise<void> {
|
|
146
|
-
return new Promise((resolve, reject) => {
|
|
147
|
-
execFile(
|
|
148
|
-
command,
|
|
149
|
-
args,
|
|
150
|
-
{ cwd: this.#appPath },
|
|
151
|
-
(error, _stdout, stderr) => {
|
|
152
|
-
if (error) {
|
|
153
|
-
if ("code" in error && error.code === "ENOENT") {
|
|
154
|
-
reject(
|
|
155
|
-
Object.assign(new Error(`${command} not found`), {
|
|
156
|
-
code: "ENOENT",
|
|
157
|
-
}),
|
|
158
|
-
);
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
const message = stderr.trim() || error.message;
|
|
162
|
-
reject(new Error(`Next.js build failed:\n${message}`));
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
resolve();
|
|
166
|
-
},
|
|
167
|
-
);
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
async #directoryExists(dirPath: string): Promise<boolean> {
|
|
172
|
-
const s = await stat(dirPath).catch(() => null);
|
|
173
|
-
return s?.isDirectory() ?? false;
|
|
174
|
-
}
|
|
93
|
+
async function directoryExists(dirPath: string): Promise<boolean> {
|
|
94
|
+
const s = await stat(dirPath).catch(() => null);
|
|
95
|
+
return s?.isDirectory() ?? false;
|
|
175
96
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { cp, mkdtemp, rm, stat } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import type { BuildArtifact, BuildStrategy } from "./build-strategy.ts";
|
|
5
|
+
import {
|
|
6
|
+
hasPackageDependency,
|
|
7
|
+
hasRootFile,
|
|
8
|
+
runPackageCli,
|
|
9
|
+
} from "./build-strategy.ts";
|
|
10
|
+
|
|
11
|
+
const NUXT_CONFIG_FILENAMES = [
|
|
12
|
+
"nuxt.config.js",
|
|
13
|
+
"nuxt.config.mjs",
|
|
14
|
+
"nuxt.config.cjs",
|
|
15
|
+
"nuxt.config.ts",
|
|
16
|
+
"nuxt.config.mts",
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Build strategy for Nuxt applications using the Nitro `node-server` preset
|
|
21
|
+
* (the default). Runs `nuxt build` and produces an artifact from `.output/`.
|
|
22
|
+
*/
|
|
23
|
+
export class NuxtBuild implements BuildStrategy {
|
|
24
|
+
readonly #appPath: string;
|
|
25
|
+
|
|
26
|
+
constructor(options: { appPath: string }) {
|
|
27
|
+
this.#appPath = options.appPath;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async canBuild(): Promise<boolean> {
|
|
31
|
+
return (
|
|
32
|
+
(await hasRootFile(this.#appPath, NUXT_CONFIG_FILENAMES)) ||
|
|
33
|
+
(await hasPackageDependency(this.#appPath, ["nuxt"]))
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async execute(): Promise<BuildArtifact> {
|
|
38
|
+
await runPackageCli({
|
|
39
|
+
appPath: this.#appPath,
|
|
40
|
+
cliName: "nuxt",
|
|
41
|
+
args: ["build"],
|
|
42
|
+
failurePrefix: "Nuxt",
|
|
43
|
+
missingMessage:
|
|
44
|
+
"Could not find the Nuxt CLI. Install it with `npm install nuxt` or ensure npx/bunx is available.",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const outputDir = path.join(this.#appPath, ".output");
|
|
48
|
+
const entryPath = path.join(outputDir, "server", "index.mjs");
|
|
49
|
+
const entryStat = await stat(entryPath).catch(() => null);
|
|
50
|
+
if (!entryStat?.isFile()) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
"Nuxt build did not produce a Nitro node server entrypoint at .output/server/index.mjs. Ensure nitro.preset is 'node-server' (the default) — set NITRO_PRESET=node-server or remove any custom preset from nuxt.config.",
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const outDir = await mkdtemp(path.join(os.tmpdir(), "compute-build-"));
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const artifactDir = path.join(outDir, "app");
|
|
60
|
+
await cp(outputDir, artifactDir, {
|
|
61
|
+
recursive: true,
|
|
62
|
+
verbatimSymlinks: true,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
directory: artifactDir,
|
|
67
|
+
entrypoint: "server/index.mjs",
|
|
68
|
+
defaultPortMapping: { http: 3000 },
|
|
69
|
+
cleanup: () => rm(outDir, { recursive: true, force: true }),
|
|
70
|
+
};
|
|
71
|
+
} catch (error) {
|
|
72
|
+
await rm(outDir, { recursive: true, force: true });
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { cp, mkdtemp, rm, stat } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import type { BuildArtifact, BuildStrategy } from "./build-strategy.ts";
|
|
5
|
+
import { hasPackageDependency, runPackageCli } from "./build-strategy.ts";
|
|
6
|
+
|
|
7
|
+
const TANSTACK_START_PACKAGES = [
|
|
8
|
+
"@tanstack/react-start",
|
|
9
|
+
"@tanstack/solid-start",
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Build strategy for TanStack Start applications targeting the Nitro node
|
|
14
|
+
* preset. Runs `vite build` and produces an artifact from `.output/`.
|
|
15
|
+
*/
|
|
16
|
+
export class TanstackStartBuild implements BuildStrategy {
|
|
17
|
+
readonly #appPath: string;
|
|
18
|
+
|
|
19
|
+
constructor(options: { appPath: string }) {
|
|
20
|
+
this.#appPath = options.appPath;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async canBuild(): Promise<boolean> {
|
|
24
|
+
return hasPackageDependency(this.#appPath, TANSTACK_START_PACKAGES);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async execute(): Promise<BuildArtifact> {
|
|
28
|
+
await runPackageCli({
|
|
29
|
+
appPath: this.#appPath,
|
|
30
|
+
cliName: "vite",
|
|
31
|
+
args: ["build"],
|
|
32
|
+
failurePrefix: "TanStack Start",
|
|
33
|
+
missingMessage:
|
|
34
|
+
"Could not find the Vite CLI. Install it with `npm install vite` or ensure npx/bunx is available.",
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const outputDir = path.join(this.#appPath, ".output");
|
|
38
|
+
const entryPath = path.join(outputDir, "server", "index.mjs");
|
|
39
|
+
const entryStat = await stat(entryPath).catch(() => null);
|
|
40
|
+
if (!entryStat?.isFile()) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
"TanStack Start build did not produce a Nitro node server entrypoint at .output/server/index.mjs. Ensure your vite.config includes the tanstackStart() and nitro() plugins with the default node preset.",
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const outDir = await mkdtemp(path.join(os.tmpdir(), "compute-build-"));
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const artifactDir = path.join(outDir, "app");
|
|
50
|
+
await cp(outputDir, artifactDir, {
|
|
51
|
+
recursive: true,
|
|
52
|
+
verbatimSymlinks: true,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
directory: artifactDir,
|
|
57
|
+
entrypoint: "server/index.mjs",
|
|
58
|
+
defaultPortMapping: { http: 3000 },
|
|
59
|
+
cleanup: () => rm(outDir, { recursive: true, force: true }),
|
|
60
|
+
};
|
|
61
|
+
} catch (error) {
|
|
62
|
+
await rm(outDir, { recursive: true, force: true });
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|