@prisma/compute-sdk 0.19.0 → 0.21.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/artifact-postprocess.d.ts +7 -0
- package/dist/artifact-postprocess.d.ts.map +1 -0
- package/dist/artifact-postprocess.js +54 -0
- package/dist/artifact-postprocess.js.map +1 -0
- 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 +17 -29
- package/dist/compute-client.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.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 +8 -4
- 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/dist/upload-artifact.d.ts +5 -0
- package/dist/upload-artifact.d.ts.map +1 -0
- package/dist/upload-artifact.js +27 -0
- package/dist/upload-artifact.js.map +1 -0
- package/package.json +1 -1
- package/src/api-client.ts +24 -8
- package/src/archive.ts +42 -10
- package/src/artifact-postprocess.ts +74 -0
- 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 -31
- package/src/index.ts +3 -0
- package/src/nextjs-build.ts +9 -4
- package/src/nuxt-build.ts +6 -4
- package/src/tanstack-start-build.ts +5 -3
- package/src/upload-artifact.ts +30 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-artifact.d.ts","sourceRoot":"","sources":["../src/upload-artifact.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,UAAU,EAChB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAsBf"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PUT a gzipped compute artifact to a Foundry-minted presigned upload URL.
|
|
3
|
+
*/
|
|
4
|
+
export async function uploadArtifact(uploadUrl, body, signal) {
|
|
5
|
+
signal?.throwIfAborted();
|
|
6
|
+
// Intentionally do not pass AbortSignal to this mutating upload request.
|
|
7
|
+
// Aborting transport can leave completion ambiguous server-side.
|
|
8
|
+
const response = await fetch(uploadUrl, {
|
|
9
|
+
method: "PUT",
|
|
10
|
+
headers: { "content-type": "application/gzip" },
|
|
11
|
+
body,
|
|
12
|
+
});
|
|
13
|
+
if (!response.ok) {
|
|
14
|
+
let message = `Artifact upload failed with HTTP ${response.status}`;
|
|
15
|
+
try {
|
|
16
|
+
const json = (await response.json());
|
|
17
|
+
if (typeof json.error === "string" && json.error.length > 0) {
|
|
18
|
+
message = `Artifact upload failed: ${json.error}`;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// ignore body parse failures
|
|
23
|
+
}
|
|
24
|
+
throw new Error(message);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=upload-artifact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-artifact.js","sourceRoot":"","sources":["../src/upload-artifact.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,SAAiB,EACjB,IAAgB,EAChB,MAAoB;IAEpB,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,yEAAyE;IACzE,iEAAiE;IACjE,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE;QACtC,MAAM,EAAE,KAAK;QACb,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI;KACL,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,IAAI,OAAO,GAAG,oCAAoC,QAAQ,CAAC,MAAM,EAAE,CAAC;QACpE,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAwB,CAAC;YAC5D,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC5D,OAAO,GAAG,2BAA2B,IAAI,CAAC,KAAK,EAAE,CAAC;YACpD,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,6BAA6B;QAC/B,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
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
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { cp, readdir, readlink, realpath, rm, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Materialize symlinks inside a build artifact that point outside the artifact
|
|
6
|
+
* directory but still live under the original app tree (typical for Next.js
|
|
7
|
+
* standalone output referencing monorepo node_modules).
|
|
8
|
+
*/
|
|
9
|
+
export async function normalizeArtifactSymlinks(
|
|
10
|
+
artifactDir: string,
|
|
11
|
+
appPath: string,
|
|
12
|
+
signal?: AbortSignal,
|
|
13
|
+
): Promise<void> {
|
|
14
|
+
signal?.throwIfAborted();
|
|
15
|
+
const normalizedArtifactDir = await realpath(path.resolve(artifactDir));
|
|
16
|
+
const normalizedAppPath = await realpath(path.resolve(appPath));
|
|
17
|
+
|
|
18
|
+
await walkDirectory(path.resolve(artifactDir));
|
|
19
|
+
|
|
20
|
+
async function walkDirectory(directory: string): Promise<void> {
|
|
21
|
+
signal?.throwIfAborted();
|
|
22
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
23
|
+
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
const fullPath = path.join(directory, entry.name);
|
|
26
|
+
|
|
27
|
+
if (entry.isDirectory()) {
|
|
28
|
+
await walkDirectory(fullPath);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!entry.isSymbolicLink()) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const target = await readlink(fullPath);
|
|
37
|
+
const resolvedTarget = path.resolve(path.dirname(fullPath), target);
|
|
38
|
+
const realTarget = await realpath(resolvedTarget);
|
|
39
|
+
|
|
40
|
+
if (isPathWithin(normalizedArtifactDir, realTarget)) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!isPathWithin(normalizedAppPath, realTarget)) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Build artifact symlink escapes the app directory: ${realTarget}`,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const targetStat = await stat(realTarget);
|
|
51
|
+
await rm(fullPath, { force: true, recursive: true });
|
|
52
|
+
await cp(realTarget, fullPath, {
|
|
53
|
+
recursive: targetStat.isDirectory(),
|
|
54
|
+
// Keep nested symlinks intact so walkDirectory can validate each one.
|
|
55
|
+
dereference: false,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (targetStat.isDirectory()) {
|
|
59
|
+
await walkDirectory(fullPath);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isPathWithin(rootPath: string, candidatePath: string): boolean {
|
|
66
|
+
const relativePath = path.relative(rootPath, candidatePath);
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
relativePath === "" ||
|
|
70
|
+
(!relativePath.startsWith(`..${path.sep}`) &&
|
|
71
|
+
relativePath !== ".." &&
|
|
72
|
+
!path.isAbsolute(relativePath))
|
|
73
|
+
);
|
|
74
|
+
}
|
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
|
@@ -48,6 +48,7 @@ import type {
|
|
|
48
48
|
VersionInfo,
|
|
49
49
|
} from "./types.ts";
|
|
50
50
|
import { REGIONS } from "./types.ts";
|
|
51
|
+
import { uploadArtifact } from "./upload-artifact.ts";
|
|
51
52
|
|
|
52
53
|
type VersionDetailData = InferOk<
|
|
53
54
|
Awaited<ReturnType<InternalApiClient["getVersion"]>>
|
|
@@ -243,7 +244,7 @@ export class ComputeClient {
|
|
|
243
244
|
options.progress?.onBuildStart?.();
|
|
244
245
|
const artifact = yield* Result.await(
|
|
245
246
|
Result.tryPromise({
|
|
246
|
-
try: () => options.strategy.execute(),
|
|
247
|
+
try: () => options.strategy.execute(options.signal),
|
|
247
248
|
catch: (e) =>
|
|
248
249
|
new BuildError({
|
|
249
250
|
message: e instanceof Error ? e.message : String(e),
|
|
@@ -258,7 +259,12 @@ export class ComputeClient {
|
|
|
258
259
|
options.progress?.onArchiveCreating?.();
|
|
259
260
|
const archiveBytes = yield* Result.await(
|
|
260
261
|
Result.tryPromise({
|
|
261
|
-
try: () =>
|
|
262
|
+
try: () =>
|
|
263
|
+
createArchive(
|
|
264
|
+
artifact.directory,
|
|
265
|
+
artifact.entrypoint,
|
|
266
|
+
options.signal,
|
|
267
|
+
),
|
|
262
268
|
catch: (e) =>
|
|
263
269
|
new ArtifactError({
|
|
264
270
|
message:
|
|
@@ -304,7 +310,7 @@ export class ComputeClient {
|
|
|
304
310
|
|
|
305
311
|
options.progress?.onUploadStart?.();
|
|
306
312
|
yield* Result.await(
|
|
307
|
-
self.#
|
|
313
|
+
self.#uploadArtifactResult(uploadUrl, archiveBytes, options.signal),
|
|
308
314
|
);
|
|
309
315
|
options.progress?.onUploadComplete?.();
|
|
310
316
|
|
|
@@ -1152,10 +1158,12 @@ export class ComputeClient {
|
|
|
1152
1158
|
}
|
|
1153
1159
|
|
|
1154
1160
|
#checkAborted(signal?: AbortSignal): Result<void, CancelledError> {
|
|
1155
|
-
|
|
1161
|
+
try {
|
|
1162
|
+
signal?.throwIfAborted();
|
|
1163
|
+
return Result.ok(undefined);
|
|
1164
|
+
} catch {
|
|
1156
1165
|
return Result.err(new CancelledError());
|
|
1157
1166
|
}
|
|
1158
|
-
return Result.ok(undefined);
|
|
1159
1167
|
}
|
|
1160
1168
|
|
|
1161
1169
|
async #resolveDeployTarget(options: {
|
|
@@ -1304,40 +1312,22 @@ export class ComputeClient {
|
|
|
1304
1312
|
});
|
|
1305
1313
|
}
|
|
1306
1314
|
|
|
1307
|
-
async #
|
|
1315
|
+
async #uploadArtifactResult(
|
|
1308
1316
|
url: string,
|
|
1309
1317
|
body: Uint8Array,
|
|
1310
1318
|
signal?: AbortSignal,
|
|
1311
1319
|
): Promise<Result<void, ArtifactError | CancelledError>> {
|
|
1312
1320
|
return Result.tryPromise({
|
|
1313
1321
|
try: async () => {
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1322
|
+
await uploadArtifact(url, body, signal);
|
|
1323
|
+
},
|
|
1324
|
+
catch: (e) => {
|
|
1325
|
+
if (signal?.aborted) return new CancelledError();
|
|
1326
|
+
return new ArtifactError({
|
|
1327
|
+
message: e instanceof Error ? e.message : String(e),
|
|
1328
|
+
cause: e,
|
|
1319
1329
|
});
|
|
1320
|
-
|
|
1321
|
-
if (!res.ok) {
|
|
1322
|
-
let message = `Artifact upload failed with HTTP ${res.status}`;
|
|
1323
|
-
try {
|
|
1324
|
-
const json = (await res.json()) as { error?: unknown };
|
|
1325
|
-
if (typeof json.error === "string" && json.error.length > 0) {
|
|
1326
|
-
message = `Artifact upload failed: ${json.error}`;
|
|
1327
|
-
}
|
|
1328
|
-
} catch {
|
|
1329
|
-
// ignore body parse failures
|
|
1330
|
-
}
|
|
1331
|
-
throw new Error(message);
|
|
1332
|
-
}
|
|
1333
1330
|
},
|
|
1334
|
-
catch: (e) =>
|
|
1335
|
-
signal?.aborted
|
|
1336
|
-
? new CancelledError()
|
|
1337
|
-
: new ArtifactError({
|
|
1338
|
-
message: e instanceof Error ? e.message : String(e),
|
|
1339
|
-
cause: e,
|
|
1340
|
-
}),
|
|
1341
1331
|
});
|
|
1342
1332
|
}
|
|
1343
1333
|
}
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,8 @@ export {
|
|
|
7
7
|
type Result,
|
|
8
8
|
TaggedError,
|
|
9
9
|
} from "better-result";
|
|
10
|
+
export { createArchive } from "./archive.ts";
|
|
11
|
+
export { normalizeArtifactSymlinks } from "./artifact-postprocess.ts";
|
|
10
12
|
export { AstroBuild } from "./astro-build.ts";
|
|
11
13
|
export { AutoBuild } from "./auto-build.ts";
|
|
12
14
|
export type { BuildArtifact, BuildStrategy } from "./build-strategy.ts";
|
|
@@ -94,3 +96,4 @@ export type {
|
|
|
94
96
|
VersionInfo,
|
|
95
97
|
} from "./types.ts";
|
|
96
98
|
export { KNOWN_REGION_IDS, REGIONS } from "./types.ts";
|
|
99
|
+
export { uploadArtifact } from "./upload-artifact.ts";
|