@prisma/compute-sdk 0.18.0 → 0.20.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/api-client.d.ts.map +1 -1
- package/dist/api-client.js +24 -8
- package/dist/api-client.js.map +1 -1
- package/dist/archive.d.ts +1 -1
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +34 -15
- package/dist/archive.js.map +1 -1
- package/dist/astro-build.d.ts +2 -2
- package/dist/astro-build.d.ts.map +1 -1
- package/dist/astro-build.js +6 -4
- package/dist/astro-build.js.map +1 -1
- package/dist/auto-build.d.ts +2 -2
- package/dist/auto-build.d.ts.map +1 -1
- package/dist/auto-build.js +7 -5
- package/dist/auto-build.js.map +1 -1
- package/dist/build-strategy.d.ts +7 -6
- package/dist/build-strategy.d.ts.map +1 -1
- package/dist/build-strategy.js +11 -7
- package/dist/build-strategy.js.map +1 -1
- package/dist/bun-build.d.ts +2 -2
- package/dist/bun-build.d.ts.map +1 -1
- package/dist/bun-build.js +9 -7
- package/dist/bun-build.js.map +1 -1
- package/dist/compute-client.d.ts.map +1 -1
- package/dist/compute-client.js +16 -9
- package/dist/compute-client.js.map +1 -1
- package/dist/nextjs-build.d.ts +2 -2
- package/dist/nextjs-build.d.ts.map +1 -1
- package/dist/nextjs-build.js +58 -8
- package/dist/nextjs-build.js.map +1 -1
- package/dist/nuxt-build.d.ts +2 -2
- package/dist/nuxt-build.d.ts.map +1 -1
- package/dist/nuxt-build.js +6 -4
- package/dist/nuxt-build.js.map +1 -1
- package/dist/tanstack-start-build.d.ts +2 -2
- package/dist/tanstack-start-build.d.ts.map +1 -1
- package/dist/tanstack-start-build.js +5 -3
- package/dist/tanstack-start-build.js.map +1 -1
- package/package.json +1 -1
- package/src/api-client.ts +24 -8
- package/src/archive.ts +42 -10
- package/src/astro-build.ts +6 -4
- package/src/auto-build.ts +7 -5
- package/src/build-strategy.ts +14 -6
- package/src/bun-build.ts +13 -6
- package/src/compute-client.ts +21 -12
- package/src/nextjs-build.ts +74 -8
- package/src/nuxt-build.ts +6 -4
- package/src/tanstack-start-build.ts +5 -3
package/src/api-client.ts
CHANGED
|
@@ -31,10 +31,12 @@ export class InternalApiClient {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
async createProject(body: CreateProjectBody, signal?: AbortSignal) {
|
|
34
|
+
signal?.throwIfAborted();
|
|
34
35
|
return this.#request(
|
|
35
36
|
this.#client.POST("/v1/projects", {
|
|
36
37
|
body: body as SdkCreateProjectBody,
|
|
37
|
-
|
|
38
|
+
// Intentionally do not pass AbortSignal for mutating operations.
|
|
39
|
+
// Aborting transport can leave remote completion ambiguous.
|
|
38
40
|
}),
|
|
39
41
|
"/v1/projects",
|
|
40
42
|
signal,
|
|
@@ -69,11 +71,13 @@ export class InternalApiClient {
|
|
|
69
71
|
body: CreateProjectServiceBody,
|
|
70
72
|
signal?: AbortSignal,
|
|
71
73
|
) {
|
|
74
|
+
signal?.throwIfAborted();
|
|
72
75
|
return this.#request(
|
|
73
76
|
this.#client.POST("/v1/projects/{projectId}/compute-services", {
|
|
74
77
|
params: { path: { projectId } },
|
|
75
78
|
body: body as SdkCreateProjectServiceBody,
|
|
76
|
-
|
|
79
|
+
// Intentionally do not pass AbortSignal for mutating operations.
|
|
80
|
+
// Aborting transport can leave remote completion ambiguous.
|
|
77
81
|
}),
|
|
78
82
|
"/v1/projects/{projectId}/compute-services",
|
|
79
83
|
signal,
|
|
@@ -95,10 +99,12 @@ export class InternalApiClient {
|
|
|
95
99
|
computeServiceId: string,
|
|
96
100
|
signal?: AbortSignal,
|
|
97
101
|
): Promise<Result<void, ApiClientError>> {
|
|
102
|
+
signal?.throwIfAborted();
|
|
98
103
|
return this.#unwrapVoid(
|
|
99
104
|
this.#client.DELETE("/v1/compute-services/{computeServiceId}", {
|
|
100
105
|
params: { path: { computeServiceId } },
|
|
101
|
-
|
|
106
|
+
// Intentionally do not pass AbortSignal for mutating operations.
|
|
107
|
+
// Aborting transport can leave remote completion ambiguous.
|
|
102
108
|
}),
|
|
103
109
|
signal,
|
|
104
110
|
);
|
|
@@ -124,11 +130,13 @@ export class InternalApiClient {
|
|
|
124
130
|
body?: CreateServiceVersionBody,
|
|
125
131
|
signal?: AbortSignal,
|
|
126
132
|
) {
|
|
133
|
+
signal?.throwIfAborted();
|
|
127
134
|
return this.#request(
|
|
128
135
|
this.#client.POST("/v1/compute-services/{computeServiceId}/versions", {
|
|
129
136
|
params: { path: { computeServiceId } },
|
|
130
137
|
body,
|
|
131
|
-
|
|
138
|
+
// Intentionally do not pass AbortSignal for mutating operations.
|
|
139
|
+
// Aborting transport can leave remote completion ambiguous.
|
|
132
140
|
}),
|
|
133
141
|
"/v1/compute-services/{computeServiceId}/versions",
|
|
134
142
|
signal,
|
|
@@ -147,10 +155,12 @@ export class InternalApiClient {
|
|
|
147
155
|
}
|
|
148
156
|
|
|
149
157
|
async startVersion(versionId: string, signal?: AbortSignal) {
|
|
158
|
+
signal?.throwIfAborted();
|
|
150
159
|
return this.#request(
|
|
151
160
|
this.#client.POST("/v1/compute-services/versions/{versionId}/start", {
|
|
152
161
|
params: { path: { versionId } },
|
|
153
|
-
|
|
162
|
+
// Intentionally do not pass AbortSignal for mutating operations.
|
|
163
|
+
// Aborting transport can leave remote completion ambiguous.
|
|
154
164
|
}),
|
|
155
165
|
"/v1/compute-services/versions/{versionId}/start",
|
|
156
166
|
signal,
|
|
@@ -161,10 +171,12 @@ export class InternalApiClient {
|
|
|
161
171
|
versionId: string,
|
|
162
172
|
signal?: AbortSignal,
|
|
163
173
|
): Promise<Result<void, ApiClientError>> {
|
|
174
|
+
signal?.throwIfAborted();
|
|
164
175
|
return this.#unwrapVoid(
|
|
165
176
|
this.#client.POST("/v1/compute-services/versions/{versionId}/stop", {
|
|
166
177
|
params: { path: { versionId } },
|
|
167
|
-
|
|
178
|
+
// Intentionally do not pass AbortSignal for mutating operations.
|
|
179
|
+
// Aborting transport can leave remote completion ambiguous.
|
|
168
180
|
}),
|
|
169
181
|
signal,
|
|
170
182
|
);
|
|
@@ -174,10 +186,12 @@ export class InternalApiClient {
|
|
|
174
186
|
versionId: string,
|
|
175
187
|
signal?: AbortSignal,
|
|
176
188
|
): Promise<Result<void, ApiClientError>> {
|
|
189
|
+
signal?.throwIfAborted();
|
|
177
190
|
return this.#unwrapVoid(
|
|
178
191
|
this.#client.DELETE("/v1/compute-services/versions/{versionId}", {
|
|
179
192
|
params: { path: { versionId } },
|
|
180
|
-
|
|
193
|
+
// Intentionally do not pass AbortSignal for mutating operations.
|
|
194
|
+
// Aborting transport can leave remote completion ambiguous.
|
|
181
195
|
}),
|
|
182
196
|
signal,
|
|
183
197
|
);
|
|
@@ -188,11 +202,13 @@ export class InternalApiClient {
|
|
|
188
202
|
body: PromoteServiceBody,
|
|
189
203
|
signal?: AbortSignal,
|
|
190
204
|
) {
|
|
205
|
+
signal?.throwIfAborted();
|
|
191
206
|
return this.#request(
|
|
192
207
|
this.#client.POST("/v1/compute-services/{computeServiceId}/promote", {
|
|
193
208
|
params: { path: { computeServiceId } },
|
|
194
209
|
body,
|
|
195
|
-
|
|
210
|
+
// Intentionally do not pass AbortSignal for mutating operations.
|
|
211
|
+
// Aborting transport can leave remote completion ambiguous.
|
|
196
212
|
}),
|
|
197
213
|
"/v1/compute-services/{computeServiceId}/promote",
|
|
198
214
|
signal,
|
package/src/archive.ts
CHANGED
|
@@ -17,12 +17,14 @@ const COMPUTE_MANIFEST_VERSION = "1" as const;
|
|
|
17
17
|
export async function createArchive(
|
|
18
18
|
directory: string,
|
|
19
19
|
entrypoint: string,
|
|
20
|
+
signal?: AbortSignal,
|
|
20
21
|
): Promise<Uint8Array> {
|
|
22
|
+
signal?.throwIfAborted();
|
|
21
23
|
const packer = pack();
|
|
22
24
|
const rootPath = path.resolve(directory);
|
|
23
25
|
|
|
24
26
|
// Walk the directory and add all files
|
|
25
|
-
await addDirectory(packer, rootPath, rootPath, "bundle");
|
|
27
|
+
await addDirectory(packer, rootPath, rootPath, "bundle", signal);
|
|
26
28
|
|
|
27
29
|
// Inject the manifest as a synthetic entry
|
|
28
30
|
const manifest = JSON.stringify(
|
|
@@ -39,7 +41,7 @@ export async function createArchive(
|
|
|
39
41
|
packer.finalize();
|
|
40
42
|
|
|
41
43
|
// Pipe through gzip and collect into buffer
|
|
42
|
-
return await collectGzipped(packer);
|
|
44
|
+
return await collectGzipped(packer, signal);
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
async function addDirectory(
|
|
@@ -47,22 +49,25 @@ async function addDirectory(
|
|
|
47
49
|
rootPath: string,
|
|
48
50
|
fsPath: string,
|
|
49
51
|
tarPrefix: string,
|
|
52
|
+
signal?: AbortSignal,
|
|
50
53
|
): Promise<void> {
|
|
54
|
+
signal?.throwIfAborted();
|
|
51
55
|
const directoryPath = resolvePathWithinRoot(rootPath, fsPath);
|
|
52
56
|
const entries = await readdir(directoryPath, { withFileTypes: true });
|
|
53
57
|
|
|
54
58
|
for (const entry of entries) {
|
|
59
|
+
signal?.throwIfAborted();
|
|
55
60
|
const fullPath = path.join(directoryPath, entry.name);
|
|
56
61
|
const tarPath = `${tarPrefix}/${entry.name}`;
|
|
57
62
|
|
|
58
63
|
if (entry.isSymbolicLink()) {
|
|
59
64
|
const linkStat = await lstat(fullPath);
|
|
60
|
-
await addSymlink(packer, rootPath, fullPath, tarPath, linkStat);
|
|
65
|
+
await addSymlink(packer, rootPath, fullPath, tarPath, linkStat, signal);
|
|
61
66
|
} else if (entry.isDirectory()) {
|
|
62
|
-
await addDirectory(packer, rootPath, fullPath, tarPath);
|
|
67
|
+
await addDirectory(packer, rootPath, fullPath, tarPath, signal);
|
|
63
68
|
} else if (entry.isFile()) {
|
|
64
69
|
const fileStat = await lstat(fullPath);
|
|
65
|
-
await addFile(packer, rootPath, fullPath, tarPath, fileStat);
|
|
70
|
+
await addFile(packer, rootPath, fullPath, tarPath, fileStat, signal);
|
|
66
71
|
}
|
|
67
72
|
}
|
|
68
73
|
}
|
|
@@ -73,7 +78,9 @@ async function addFile(
|
|
|
73
78
|
fsPath: string,
|
|
74
79
|
tarPath: string,
|
|
75
80
|
fileStat: { size: number; mode: number; mtime: Date },
|
|
81
|
+
signal?: AbortSignal,
|
|
76
82
|
): Promise<void> {
|
|
83
|
+
signal?.throwIfAborted();
|
|
77
84
|
const filePath = resolvePathWithinRoot(rootPath, fsPath);
|
|
78
85
|
const content = await readFile(filePath);
|
|
79
86
|
packer.entry(
|
|
@@ -93,13 +100,16 @@ async function addSymlink(
|
|
|
93
100
|
fsPath: string,
|
|
94
101
|
tarPath: string,
|
|
95
102
|
linkStat: { mode: number; mtime: Date },
|
|
103
|
+
signal?: AbortSignal,
|
|
96
104
|
): Promise<void> {
|
|
105
|
+
signal?.throwIfAborted();
|
|
97
106
|
const symlinkPath = resolvePathWithinRoot(rootPath, fsPath);
|
|
98
107
|
const target = await readlink(symlinkPath);
|
|
99
108
|
const linkname = await resolveArchiveSymlinkTarget(
|
|
100
109
|
rootPath,
|
|
101
110
|
symlinkPath,
|
|
102
111
|
target,
|
|
112
|
+
signal,
|
|
103
113
|
);
|
|
104
114
|
|
|
105
115
|
packer.entry({
|
|
@@ -115,7 +125,9 @@ async function resolveArchiveSymlinkTarget(
|
|
|
115
125
|
rootPath: string,
|
|
116
126
|
symlinkPath: string,
|
|
117
127
|
target: string,
|
|
128
|
+
signal?: AbortSignal,
|
|
118
129
|
): Promise<string> {
|
|
130
|
+
signal?.throwIfAborted();
|
|
119
131
|
const symlinkDir = path.dirname(symlinkPath);
|
|
120
132
|
const resolvedTarget = resolvePathWithinRoot(
|
|
121
133
|
rootPath,
|
|
@@ -163,11 +175,27 @@ function resolvePathWithinRoot(rootPath: string, fsPath: string): string {
|
|
|
163
175
|
return resolvedPath;
|
|
164
176
|
}
|
|
165
177
|
|
|
166
|
-
function collectGzipped(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
178
|
+
function collectGzipped(
|
|
179
|
+
packer: Pack,
|
|
180
|
+
signal?: AbortSignal,
|
|
181
|
+
): Promise<Uint8Array> {
|
|
182
|
+
signal?.throwIfAborted();
|
|
183
|
+
|
|
184
|
+
const chunks: Buffer[] = [];
|
|
185
|
+
const gzip = createGzip();
|
|
186
|
+
const onAbort = () => {
|
|
187
|
+
// AbortSignal.abort() sets signal.reason; fallback keeps a stream error path if a non-standard signal omits it.
|
|
188
|
+
const abortError =
|
|
189
|
+
signal?.reason instanceof Error
|
|
190
|
+
? signal.reason
|
|
191
|
+
: new DOMException("The operation was aborted", "AbortError");
|
|
192
|
+
gzip.destroy(abortError);
|
|
193
|
+
packer.destroy(abortError);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
197
|
+
|
|
198
|
+
const promise = new Promise<Uint8Array>((resolve, reject) => {
|
|
171
199
|
packer.pipe(gzip);
|
|
172
200
|
|
|
173
201
|
gzip.on("data", (chunk: Buffer) => {
|
|
@@ -181,4 +209,8 @@ function collectGzipped(packer: Pack): Promise<Uint8Array> {
|
|
|
181
209
|
gzip.on("error", reject);
|
|
182
210
|
packer.on("error", reject);
|
|
183
211
|
});
|
|
212
|
+
|
|
213
|
+
return promise.finally(() => {
|
|
214
|
+
signal?.removeEventListener("abort", onAbort);
|
|
215
|
+
});
|
|
184
216
|
}
|
package/src/astro-build.ts
CHANGED
|
@@ -27,14 +27,15 @@ export class AstroBuild implements BuildStrategy {
|
|
|
27
27
|
this.#appPath = options.appPath;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
async canBuild(): Promise<boolean> {
|
|
30
|
+
async canBuild(signal?: AbortSignal): Promise<boolean> {
|
|
31
31
|
return (
|
|
32
|
-
(await hasRootFile(this.#appPath, ASTRO_CONFIG_FILENAMES)) ||
|
|
33
|
-
(await hasPackageDependency(this.#appPath, ["astro"]))
|
|
32
|
+
(await hasRootFile(this.#appPath, ASTRO_CONFIG_FILENAMES, signal)) ||
|
|
33
|
+
(await hasPackageDependency(this.#appPath, ["astro"], signal))
|
|
34
34
|
);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
async execute(): Promise<BuildArtifact> {
|
|
37
|
+
async execute(signal?: AbortSignal): Promise<BuildArtifact> {
|
|
38
|
+
signal?.throwIfAborted();
|
|
38
39
|
await runPackageCli({
|
|
39
40
|
appPath: this.#appPath,
|
|
40
41
|
cliName: "astro",
|
|
@@ -42,6 +43,7 @@ export class AstroBuild implements BuildStrategy {
|
|
|
42
43
|
failurePrefix: "Astro",
|
|
43
44
|
missingMessage:
|
|
44
45
|
"Could not find the Astro CLI. Install it with `npm install astro` or ensure npx/bunx is available.",
|
|
46
|
+
signal,
|
|
45
47
|
});
|
|
46
48
|
|
|
47
49
|
const distDir = path.join(this.#appPath, "dist");
|
package/src/auto-build.ts
CHANGED
|
@@ -25,19 +25,21 @@ export class AutoBuild implements BuildStrategy {
|
|
|
25
25
|
];
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
async canBuild(): Promise<boolean> {
|
|
28
|
+
async canBuild(signal?: AbortSignal): Promise<boolean> {
|
|
29
29
|
for (const strategy of this.#strategies) {
|
|
30
|
-
|
|
30
|
+
signal?.throwIfAborted();
|
|
31
|
+
if (await strategy.canBuild(signal)) {
|
|
31
32
|
return true;
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
return false;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
async execute(): Promise<BuildArtifact> {
|
|
38
|
+
async execute(signal?: AbortSignal): Promise<BuildArtifact> {
|
|
38
39
|
for (const strategy of this.#strategies) {
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
signal?.throwIfAborted();
|
|
41
|
+
if (await strategy.canBuild(signal)) {
|
|
42
|
+
return strategy.execute(signal);
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
45
|
throw new Error("No suitable build strategy found for this application");
|
package/src/build-strategy.ts
CHANGED
|
@@ -32,8 +32,8 @@ export interface BuildArtifact {
|
|
|
32
32
|
* A build strategy produces a deployable artifact.
|
|
33
33
|
*/
|
|
34
34
|
export interface BuildStrategy {
|
|
35
|
-
canBuild(): Promise<boolean>;
|
|
36
|
-
execute(): Promise<BuildArtifact>;
|
|
35
|
+
canBuild(signal?: AbortSignal): Promise<boolean>;
|
|
36
|
+
execute(signal?: AbortSignal): Promise<BuildArtifact>;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
@@ -49,11 +49,12 @@ export class PreBuilt implements BuildStrategy {
|
|
|
49
49
|
this.#entrypoint = options.entrypoint;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
async canBuild(): Promise<boolean> {
|
|
52
|
+
async canBuild(_signal?: AbortSignal): Promise<boolean> {
|
|
53
53
|
return true;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
async execute(): Promise<BuildArtifact> {
|
|
56
|
+
async execute(signal?: AbortSignal): Promise<BuildArtifact> {
|
|
57
|
+
signal?.throwIfAborted();
|
|
57
58
|
const normalized = path.normalize(this.#entrypoint);
|
|
58
59
|
|
|
59
60
|
if (path.isAbsolute(normalized)) {
|
|
@@ -92,7 +93,9 @@ export class PreBuilt implements BuildStrategy {
|
|
|
92
93
|
export async function hasRootFile(
|
|
93
94
|
appPath: string,
|
|
94
95
|
filenames: readonly string[],
|
|
96
|
+
signal?: AbortSignal,
|
|
95
97
|
): Promise<boolean> {
|
|
98
|
+
signal?.throwIfAborted();
|
|
96
99
|
let entries: string[];
|
|
97
100
|
try {
|
|
98
101
|
entries = await readdir(appPath);
|
|
@@ -110,7 +113,9 @@ export async function hasRootFile(
|
|
|
110
113
|
export async function hasPackageDependency(
|
|
111
114
|
appPath: string,
|
|
112
115
|
packageNames: readonly string[],
|
|
116
|
+
signal?: AbortSignal,
|
|
113
117
|
): Promise<boolean> {
|
|
118
|
+
signal?.throwIfAborted();
|
|
114
119
|
let content: string;
|
|
115
120
|
try {
|
|
116
121
|
content = await readFile(path.join(appPath, "package.json"), "utf-8");
|
|
@@ -154,6 +159,7 @@ export async function runPackageCli(opts: {
|
|
|
154
159
|
failurePrefix: string;
|
|
155
160
|
/** Thrown when no launcher is available. */
|
|
156
161
|
missingMessage: string;
|
|
162
|
+
signal?: AbortSignal;
|
|
157
163
|
}): Promise<void> {
|
|
158
164
|
const localBin = path.join(
|
|
159
165
|
opts.appPath,
|
|
@@ -168,8 +174,9 @@ export async function runPackageCli(opts: {
|
|
|
168
174
|
];
|
|
169
175
|
|
|
170
176
|
for (const { command, args } of candidates) {
|
|
177
|
+
opts.signal?.throwIfAborted();
|
|
171
178
|
try {
|
|
172
|
-
await exec(command, args, opts.appPath, opts.failurePrefix);
|
|
179
|
+
await exec(command, args, opts.appPath, opts.failurePrefix, opts.signal);
|
|
173
180
|
return;
|
|
174
181
|
} catch (error) {
|
|
175
182
|
if (isENOENT(error)) continue;
|
|
@@ -197,9 +204,10 @@ function exec(
|
|
|
197
204
|
args: string[],
|
|
198
205
|
cwd: string,
|
|
199
206
|
failurePrefix: string,
|
|
207
|
+
signal?: AbortSignal,
|
|
200
208
|
): Promise<void> {
|
|
201
209
|
return new Promise((resolve, reject) => {
|
|
202
|
-
execFile(command, args, { cwd }, (error, _stdout, stderr) => {
|
|
210
|
+
execFile(command, args, { cwd, signal }, (error, _stdout, stderr) => {
|
|
203
211
|
if (error) {
|
|
204
212
|
if ("code" in error && error.code === "ENOENT") {
|
|
205
213
|
reject(
|
package/src/bun-build.ts
CHANGED
|
@@ -24,12 +24,13 @@ export class BunBuild implements BuildStrategy {
|
|
|
24
24
|
this.#entrypoint = options.entrypoint;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
async canBuild(): Promise<boolean> {
|
|
27
|
+
async canBuild(_signal?: AbortSignal): Promise<boolean> {
|
|
28
28
|
return true;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
async execute(): Promise<BuildArtifact> {
|
|
32
|
-
|
|
31
|
+
async execute(signal?: AbortSignal): Promise<BuildArtifact> {
|
|
32
|
+
signal?.throwIfAborted();
|
|
33
|
+
const entrypoint = await this.#resolveEntrypoint(signal);
|
|
33
34
|
|
|
34
35
|
const outDir = await mkdtemp(path.join(os.tmpdir(), "compute-build-"));
|
|
35
36
|
const bundleDir = path.join(outDir, "bundle");
|
|
@@ -38,7 +39,7 @@ export class BunBuild implements BuildStrategy {
|
|
|
38
39
|
const absoluteEntrypoint = path.join(this.#appPath, entrypoint);
|
|
39
40
|
|
|
40
41
|
try {
|
|
41
|
-
await this.#runBuild(absoluteEntrypoint, bundleDir);
|
|
42
|
+
await this.#runBuild(absoluteEntrypoint, bundleDir, signal);
|
|
42
43
|
} catch (error) {
|
|
43
44
|
await rm(outDir, { recursive: true, force: true });
|
|
44
45
|
throw error;
|
|
@@ -62,7 +63,8 @@ export class BunBuild implements BuildStrategy {
|
|
|
62
63
|
};
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
async #resolveEntrypoint(): Promise<string> {
|
|
66
|
+
async #resolveEntrypoint(signal?: AbortSignal): Promise<string> {
|
|
67
|
+
signal?.throwIfAborted();
|
|
66
68
|
const candidate = this.#entrypoint ?? (await this.#readPackageJsonMain());
|
|
67
69
|
|
|
68
70
|
if (!candidate) {
|
|
@@ -125,7 +127,11 @@ export class BunBuild implements BuildStrategy {
|
|
|
125
127
|
return typeof parsed.main === "string" ? parsed.main : undefined;
|
|
126
128
|
}
|
|
127
129
|
|
|
128
|
-
#runBuild(
|
|
130
|
+
#runBuild(
|
|
131
|
+
absoluteEntrypoint: string,
|
|
132
|
+
bundleDir: string,
|
|
133
|
+
signal?: AbortSignal,
|
|
134
|
+
): Promise<void> {
|
|
129
135
|
return new Promise((resolve, reject) => {
|
|
130
136
|
execFile(
|
|
131
137
|
"bun",
|
|
@@ -138,6 +144,7 @@ export class BunBuild implements BuildStrategy {
|
|
|
138
144
|
"bun",
|
|
139
145
|
"--sourcemap=external",
|
|
140
146
|
],
|
|
147
|
+
{ signal },
|
|
141
148
|
(error, _stdout, stderr) => {
|
|
142
149
|
if (error) {
|
|
143
150
|
if ("code" in error && error.code === "ENOENT") {
|
package/src/compute-client.ts
CHANGED
|
@@ -243,7 +243,7 @@ export class ComputeClient {
|
|
|
243
243
|
options.progress?.onBuildStart?.();
|
|
244
244
|
const artifact = yield* Result.await(
|
|
245
245
|
Result.tryPromise({
|
|
246
|
-
try: () => options.strategy.execute(),
|
|
246
|
+
try: () => options.strategy.execute(options.signal),
|
|
247
247
|
catch: (e) =>
|
|
248
248
|
new BuildError({
|
|
249
249
|
message: e instanceof Error ? e.message : String(e),
|
|
@@ -258,7 +258,12 @@ export class ComputeClient {
|
|
|
258
258
|
options.progress?.onArchiveCreating?.();
|
|
259
259
|
const archiveBytes = yield* Result.await(
|
|
260
260
|
Result.tryPromise({
|
|
261
|
-
try: () =>
|
|
261
|
+
try: () =>
|
|
262
|
+
createArchive(
|
|
263
|
+
artifact.directory,
|
|
264
|
+
artifact.entrypoint,
|
|
265
|
+
options.signal,
|
|
266
|
+
),
|
|
262
267
|
catch: (e) =>
|
|
263
268
|
new ArtifactError({
|
|
264
269
|
message:
|
|
@@ -1152,10 +1157,12 @@ export class ComputeClient {
|
|
|
1152
1157
|
}
|
|
1153
1158
|
|
|
1154
1159
|
#checkAborted(signal?: AbortSignal): Result<void, CancelledError> {
|
|
1155
|
-
|
|
1160
|
+
try {
|
|
1161
|
+
signal?.throwIfAborted();
|
|
1162
|
+
return Result.ok(undefined);
|
|
1163
|
+
} catch {
|
|
1156
1164
|
return Result.err(new CancelledError());
|
|
1157
1165
|
}
|
|
1158
|
-
return Result.ok(undefined);
|
|
1159
1166
|
}
|
|
1160
1167
|
|
|
1161
1168
|
async #resolveDeployTarget(options: {
|
|
@@ -1311,11 +1318,13 @@ export class ComputeClient {
|
|
|
1311
1318
|
): Promise<Result<void, ArtifactError | CancelledError>> {
|
|
1312
1319
|
return Result.tryPromise({
|
|
1313
1320
|
try: async () => {
|
|
1321
|
+
signal?.throwIfAborted();
|
|
1322
|
+
// Intentionally do not pass AbortSignal to this mutating upload request.
|
|
1323
|
+
// Aborting transport can leave completion ambiguous server-side.
|
|
1314
1324
|
const res = await fetch(url, {
|
|
1315
1325
|
method: "PUT",
|
|
1316
1326
|
headers: { "content-type": "application/gzip" },
|
|
1317
1327
|
body,
|
|
1318
|
-
signal,
|
|
1319
1328
|
});
|
|
1320
1329
|
|
|
1321
1330
|
if (!res.ok) {
|
|
@@ -1331,13 +1340,13 @@ export class ComputeClient {
|
|
|
1331
1340
|
throw new Error(message);
|
|
1332
1341
|
}
|
|
1333
1342
|
},
|
|
1334
|
-
catch: (e) =>
|
|
1335
|
-
signal?.aborted
|
|
1336
|
-
|
|
1337
|
-
:
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1343
|
+
catch: (e) => {
|
|
1344
|
+
if (signal?.aborted) return new CancelledError();
|
|
1345
|
+
return new ArtifactError({
|
|
1346
|
+
message: e instanceof Error ? e.message : String(e),
|
|
1347
|
+
cause: e,
|
|
1348
|
+
});
|
|
1349
|
+
},
|
|
1341
1350
|
});
|
|
1342
1351
|
}
|
|
1343
1352
|
}
|
package/src/nextjs-build.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cp, mkdtemp, rm, stat } from "node:fs/promises";
|
|
1
|
+
import { cp, mkdtemp, readdir, rm, stat } from "node:fs/promises";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import type { BuildArtifact, BuildStrategy } from "./build-strategy.ts";
|
|
@@ -26,14 +26,15 @@ export class NextjsBuild implements BuildStrategy {
|
|
|
26
26
|
this.#appPath = options.appPath;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
async canBuild(): Promise<boolean> {
|
|
29
|
+
async canBuild(signal?: AbortSignal): Promise<boolean> {
|
|
30
30
|
return (
|
|
31
|
-
(await hasRootFile(this.#appPath, NEXT_CONFIG_FILENAMES)) ||
|
|
32
|
-
(await hasPackageDependency(this.#appPath, ["next"]))
|
|
31
|
+
(await hasRootFile(this.#appPath, NEXT_CONFIG_FILENAMES, signal)) ||
|
|
32
|
+
(await hasPackageDependency(this.#appPath, ["next"], signal))
|
|
33
33
|
);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
async execute(): Promise<BuildArtifact> {
|
|
36
|
+
async execute(signal?: AbortSignal): Promise<BuildArtifact> {
|
|
37
|
+
signal?.throwIfAborted();
|
|
37
38
|
await runPackageCli({
|
|
38
39
|
appPath: this.#appPath,
|
|
39
40
|
cliName: "next",
|
|
@@ -41,6 +42,7 @@ export class NextjsBuild implements BuildStrategy {
|
|
|
41
42
|
failurePrefix: "Next.js",
|
|
42
43
|
missingMessage:
|
|
43
44
|
"Could not find the Next.js CLI. Install it with `npm install next` or ensure npx/bunx is available.",
|
|
45
|
+
signal,
|
|
44
46
|
});
|
|
45
47
|
|
|
46
48
|
const standaloneDir = path.join(this.#appPath, ".next", "standalone");
|
|
@@ -61,9 +63,20 @@ export class NextjsBuild implements BuildStrategy {
|
|
|
61
63
|
verbatimSymlinks: true,
|
|
62
64
|
});
|
|
63
65
|
|
|
66
|
+
// In monorepos, Next preserves the workspace-relative path from the
|
|
67
|
+
// workspace root down to the app, so server.js lands at e.g.
|
|
68
|
+
// .next/standalone/apps/web/server.js, not at the standalone root.
|
|
69
|
+
// The runtime needs the correct entrypoint, and Next expects public/
|
|
70
|
+
// and .next/static/ to be siblings of server.js.
|
|
71
|
+
const serverSubpath = await findServerSubpath(artifactDir);
|
|
72
|
+
const serverDir =
|
|
73
|
+
serverSubpath === undefined
|
|
74
|
+
? artifactDir
|
|
75
|
+
: path.join(artifactDir, serverSubpath);
|
|
76
|
+
|
|
64
77
|
const publicDir = path.join(this.#appPath, "public");
|
|
65
78
|
if (await directoryExists(publicDir)) {
|
|
66
|
-
await cp(publicDir, path.join(
|
|
79
|
+
await cp(publicDir, path.join(serverDir, "public"), {
|
|
67
80
|
recursive: true,
|
|
68
81
|
verbatimSymlinks: true,
|
|
69
82
|
});
|
|
@@ -71,15 +84,20 @@ export class NextjsBuild implements BuildStrategy {
|
|
|
71
84
|
|
|
72
85
|
const staticDir = path.join(this.#appPath, ".next", "static");
|
|
73
86
|
if (await directoryExists(staticDir)) {
|
|
74
|
-
await cp(staticDir, path.join(
|
|
87
|
+
await cp(staticDir, path.join(serverDir, ".next", "static"), {
|
|
75
88
|
recursive: true,
|
|
76
89
|
verbatimSymlinks: true,
|
|
77
90
|
});
|
|
78
91
|
}
|
|
79
92
|
|
|
93
|
+
const entrypoint =
|
|
94
|
+
serverSubpath === undefined
|
|
95
|
+
? "server.js"
|
|
96
|
+
: path.posix.join(serverSubpath, "server.js");
|
|
97
|
+
|
|
80
98
|
return {
|
|
81
99
|
directory: artifactDir,
|
|
82
|
-
entrypoint
|
|
100
|
+
entrypoint,
|
|
83
101
|
defaultPortMapping: { http: 3000 },
|
|
84
102
|
cleanup: () => rm(outDir, { recursive: true, force: true }),
|
|
85
103
|
};
|
|
@@ -94,3 +112,51 @@ async function directoryExists(dirPath: string): Promise<boolean> {
|
|
|
94
112
|
const s = await stat(dirPath).catch(() => null);
|
|
95
113
|
return s?.isDirectory() ?? false;
|
|
96
114
|
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Scan the Next.js standalone output to find the directory that contains
|
|
118
|
+
* server.js, relative to the standalone root. Returns undefined when
|
|
119
|
+
* server.js is at the root (single-app repos) and a posix-joined subpath
|
|
120
|
+
* like "apps/web" for monorepos. node_modules is skipped because
|
|
121
|
+
* third-party packages may ship a file named server.js.
|
|
122
|
+
*/
|
|
123
|
+
async function findServerSubpath(
|
|
124
|
+
standaloneDir: string,
|
|
125
|
+
): Promise<string | undefined> {
|
|
126
|
+
const matches: (string | undefined)[] = [];
|
|
127
|
+
|
|
128
|
+
async function walk(
|
|
129
|
+
currentDir: string,
|
|
130
|
+
relative: string | undefined,
|
|
131
|
+
): Promise<void> {
|
|
132
|
+
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
133
|
+
for (const entry of entries) {
|
|
134
|
+
if (entry.name === "node_modules") continue;
|
|
135
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
136
|
+
if (entry.isDirectory()) {
|
|
137
|
+
const nextRelative =
|
|
138
|
+
relative === undefined
|
|
139
|
+
? entry.name
|
|
140
|
+
: path.posix.join(relative, entry.name);
|
|
141
|
+
await walk(fullPath, nextRelative);
|
|
142
|
+
} else if (entry.isFile() && entry.name === "server.js") {
|
|
143
|
+
matches.push(relative);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
await walk(standaloneDir, undefined);
|
|
149
|
+
|
|
150
|
+
if (matches.length > 1) {
|
|
151
|
+
const locations = matches.map((m) => m ?? "<root>").join(", ");
|
|
152
|
+
throw new Error(
|
|
153
|
+
`Next.js standalone output has multiple server.js files (${locations}). Cannot determine the application entrypoint.`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
if (matches.length === 0) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
"Next.js standalone output is missing server.js. The build may not have completed successfully.",
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
return matches[0];
|
|
162
|
+
}
|
package/src/nuxt-build.ts
CHANGED
|
@@ -27,14 +27,15 @@ export class NuxtBuild implements BuildStrategy {
|
|
|
27
27
|
this.#appPath = options.appPath;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
async canBuild(): Promise<boolean> {
|
|
30
|
+
async canBuild(signal?: AbortSignal): Promise<boolean> {
|
|
31
31
|
return (
|
|
32
|
-
(await hasRootFile(this.#appPath, NUXT_CONFIG_FILENAMES)) ||
|
|
33
|
-
(await hasPackageDependency(this.#appPath, ["nuxt"]))
|
|
32
|
+
(await hasRootFile(this.#appPath, NUXT_CONFIG_FILENAMES, signal)) ||
|
|
33
|
+
(await hasPackageDependency(this.#appPath, ["nuxt"], signal))
|
|
34
34
|
);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
async execute(): Promise<BuildArtifact> {
|
|
37
|
+
async execute(signal?: AbortSignal): Promise<BuildArtifact> {
|
|
38
|
+
signal?.throwIfAborted();
|
|
38
39
|
await runPackageCli({
|
|
39
40
|
appPath: this.#appPath,
|
|
40
41
|
cliName: "nuxt",
|
|
@@ -42,6 +43,7 @@ export class NuxtBuild implements BuildStrategy {
|
|
|
42
43
|
failurePrefix: "Nuxt",
|
|
43
44
|
missingMessage:
|
|
44
45
|
"Could not find the Nuxt CLI. Install it with `npm install nuxt` or ensure npx/bunx is available.",
|
|
46
|
+
signal,
|
|
45
47
|
});
|
|
46
48
|
|
|
47
49
|
const outputDir = path.join(this.#appPath, ".output");
|
|
@@ -20,11 +20,12 @@ export class TanstackStartBuild implements BuildStrategy {
|
|
|
20
20
|
this.#appPath = options.appPath;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
async canBuild(): Promise<boolean> {
|
|
24
|
-
return hasPackageDependency(this.#appPath, TANSTACK_START_PACKAGES);
|
|
23
|
+
async canBuild(signal?: AbortSignal): Promise<boolean> {
|
|
24
|
+
return hasPackageDependency(this.#appPath, TANSTACK_START_PACKAGES, signal);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
async execute(): Promise<BuildArtifact> {
|
|
27
|
+
async execute(signal?: AbortSignal): Promise<BuildArtifact> {
|
|
28
|
+
signal?.throwIfAborted();
|
|
28
29
|
await runPackageCli({
|
|
29
30
|
appPath: this.#appPath,
|
|
30
31
|
cliName: "vite",
|
|
@@ -32,6 +33,7 @@ export class TanstackStartBuild implements BuildStrategy {
|
|
|
32
33
|
failurePrefix: "TanStack Start",
|
|
33
34
|
missingMessage:
|
|
34
35
|
"Could not find the Vite CLI. Install it with `npm install vite` or ensure npx/bunx is available.",
|
|
36
|
+
signal,
|
|
35
37
|
});
|
|
36
38
|
|
|
37
39
|
const outputDir = path.join(this.#appPath, ".output");
|